Python Software Security ~ OS Command Injection #DevSecOps ~ Ar-Ge

Suppressor

Request Uzmanı
16 Kas 2022
1,245
790
always, everywhere
p0qbke2.png


~

Herkese selamlar, ben Suppressor. Bugün sizlere bilinmeyen ama basit bir açıktan bahsedeceğim. Zafiyetin adı "OS Command Injection" yani komut enjeksiyonu. İlk olarak zafiyeti tanıyalım. Zafiyetin isminden de anlaşılacağı gibi, bir işletim sistemi komutlarını çalıştırmamıza olanak sağlayan bir açıktır. Örneğin Linux'ta (cd, ls) veya Windows'ta (cls, dir) cmd ile ping atmayı deneyelim.


475rn3p.png



Evet, isteğimiz başarılı. Peki, bunu Flask ile bir web sitesi üzerinden yapacak olsak?

İlk olarak gerekli dosya ve klasörleri oluşturalım:
  • app.py
  • templates/index.html


app.py

Python:
from flask import Flask, request, render_template
import os

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/ping', methods=['POST'])
def ping():
    ip = request.form['ip']
    command = f"ping  {ip}"
    result = os.popen(command).read()
    return render_template('index.html', response=result)

if __name__ == '__main__':
    app.run(debug=True)


HTML

HTML:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body{
            background-color: black;
            text-align: center;
        }
        p{
            color:  white;
        }
    </style>
</head>
<body>


    <form action="/ping" method="post">
        <p>IP Address:</p> <input type="text" name="ip">
        <input type="submit" value="Ping">
    </form>
    <br>
    <p>{{ response }}</p>
</body>
</html>



ctf0sk9.png

umBqNLP.png


Kodların açıklanması ve zafiyetin kaynağı:

Önemli bir not: Bu açık PHP'de daha mümkündür ve daha basit bir şekilde gerçekleştirilebilir. Bunun için Python'u biraz zorladım. Örneğin, subprocessi kullanırsanız herhangi bir sorunla karşılaşmazsınız. Ayrıca Python ile böyle bir zafiyetin oluşması oldukça zordur.


Kodları web üzerinden göstermek için Flask, form verilerini almak için requests, bir index göstermek için render_template, sistem komutu çalıştırmak için ise os ekliyoruz.
Python:
from flask import Flask, request, render_template
import os


İlk satırda flask nesnesini tanımlıyoruz arıdann route dekaratörü ile kök dizine bir yani ana url ye bir istek gittiği zaman index.html i return ediyoruz
Python:
app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

Sistemde kodu çalıştırmak için bir eylem olması gerekmekte. Python Flask'te, JavaScript'teki onclick gibi fonksiyonlar olmadığı için /ping diye bir dizine POST request gelirse ilerliyoruz.
Python:
@app.route('/ping', methods=['POST'])

İlgili dizine POST request gelirse, requesti kullanarak form değerlerini çekeceğiz. İstek geldiğinde, IP ismini taşıyan form değerinin verisini alıp, ip isimli bir değişkene tanımlıyoruz.
Python:
def ping():
    ip = request.form['ip']


Ardından command diye bir değişken tanımlayıp, ping attırma işlemini gerçekleştirmek için gerekli satırı tamamlıyoruz. Yani command değeri şu an ping Formdan_gelen_değer şeklinde. İlk olarak result diye bir değişken tanımlıyoruz ve ardından kodları yazıyoruz. os.popen fonksiyonu bize kısaca bir komutu çalıştırmamıza ve çıktısını almamıza yarar. read kısmı ise komutun çıktısını (response) bize string olarak döndürür. index.html'in tekrar kullanıcıya gösterilmesi ve verdiğimiz kodun çıktısını HTML'de görüntüleyebilmek için response diye bir parametre tanımlayıp, result değişkenini ona yazıyorum.
Python:
    command = f"ping  {ip}"
    result = os.popen(command).read()
    return render_template('index.html', response=result)


Kod bir kütüphane olarak eklenmeyip, direkt olarak çalıştırıldıysa kodu çalıştır :
Python:
if __name__ == '__main__':
    app.run(debug=True)

or14aef.png



Zafiyete biraz daha detaylı bakalım




Zafiyet ise tam olarak burada kaynaklanıyor. Herhangi bir karakter filtrelemesi yok. Kodumuz şu an şöyle çalışıyor:


Zafiyetli satır

Python:
result = os.popen(command).read()



nc5o9kn.png


Evet, böyle bir komut çalışıyor ama filtreleme yok. Ya birisi böyle bir şey yaparsa:
Rich (BB code):
ping 8.8.8.8 && dir

Artık "&&" operatörü kullanıldığı için, ilk komut tamamlandıktan sonra dir komutunu çalıştıracak ve zafiyet varsa makinedeki dosyalar görüntülenebilecek.
gan5qpm.png

R&D = Research and development = Arge
🤔

ctf0sk9.png


umBqNLP.png


Zafiyeti filtrelemek



Burada regex/re denilen bir zımbırtı kullanıyoruz. Yakında ne işe yaradığını anlatacağım ama kısaca bu zımbırtı filtreleme işlemlerinde kullanılıyor. Güzel de bir kütüphanedir, evlenmek gibi bir niyetiniz varsa oldukça delikanlıdır. Kısaca işlevi, gelen input'un bir IP adresi olup olmadığını kontrol etmek.




Python:
from flask import Flask, request, render_template
import subprocess
import re

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/ping', methods=['POST'])
def ping():
    ip = request.form['ip']
    
    if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip):
        return render_template('index.html', response="Geçersiz IP adresi")

    try:
        result = subprocess.check_output(['ping', ip], universal_newlines=True)
    except subprocess.CalledProcessError as e:
        result = f"Hata: {e}"

    return render_template('index.html', response=result)

if __name__ == '__main__':
    app.run(debug=True)
ctf0sk9.png


umBqNLP.png

 
Son düzenleme:

legandrary

Üye
24 Ağu 2023
181
38
Arkandayım
p0qbke2.png



★?


Herkese selamlar, ben Suppressor. Bugün sizlere bilinmeyen ama basit bir açıktan bahsedeceğim. Zafiyetin adı "OS Command Injection" yani komut enjeksiyonu. İlk olarak zafiyeti tanıyalım. Zafiyetin isminden de anlaşılacağı gibi, bir işletim sistemi komutlarını çalıştırmamıza olanak sağlayan bir açıktır. Örneğin Linux'ta (cd, ls) veya Windows'ta (cls, dir) cmd ile ping atmayı deneyelim.


475rn3p.png



Evet, isteğimiz başarılı. Peki, bunu Flask ile bir web sitesi üzerinden yapacak olsak?

İlk olarak gerekli dosya ve klasörleri oluşturalım:
  • app.py
  • templates/index.html


app.py

Python:
from flask import Flask, request, render_template
import os

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/ping', methods=['POST'])
def ping():
    ip = request.form['ip']
    command = f"ping  {ip}"
    result = os.popen(command).read()
    return render_template('index.html', response=result)

if __name__ == '__main__':
    app.run(debug=True)


HTML

HTML:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body{
            background-color: black;
            text-align: center;
        }
        p{
            color:  white;
        }
    </style>
</head>
<body>


    <form action="/ping" method="post">
        <p>IP Address:</p> <input type="text" name="ip">
        <input type="submit" value="Ping">
    </form>
    <br>
    <p>{{ response }}</p>
</body>
</html>



ctf0sk9.png

umBqNLP.png


Kodların açıklanması ve zafiyetin kaynağı:

Önemli bir not: Bu açık PHP'de daha mümkündür ve daha basit bir şekilde gerçekleştirilebilir. Bunun için Python'u biraz zorladım. Örneğin, subprocessi kullanırsanız herhangi bir sorunla karşılaşmazsınız. Ayrıca Python ile böyle bir zafiyetin oluşması oldukça zordur.


Kodları web üzerinden göstermek için Flask, form verilerini almak için requests, bir index göstermek için render_template, sistem komutu çalıştırmak için ise os ekliyoruz.
Python:
from flask import Flask, request, render_template
import os


İlk satırda flask nesnesini tanımlıyoruz arıdann route dekaratörü ile kök dizine bir yani ana url ye bir istek gittiği zaman index.html i return ediyoruz
Python:
app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

Sistemde kodu çalıştırmak için bir eylem olması gerekmekte. Python Flask'te, JavaScript'teki onclick gibi fonksiyonlar olmadığı için /ping diye bir dizine POST request gelirse ilerliyoruz.
Python:
@app.route('/ping', methods=['POST'])

İlgili dizine POST request gelirse, requesti kullanarak form değerlerini çekeceğiz. İstek geldiğinde, IP ismini taşıyan form değerinin verisini alıp, ip isimli bir değişkene tanımlıyoruz.
Python:
def ping():
    ip = request.form['ip']


Ardından command diye bir değişken tanımlayıp, ping attırma işlemini gerçekleştirmek için gerekli satırı tamamlıyoruz. Yani command değeri şu an ping Formdan_gelen_değer şeklinde. İlk olarak result diye bir değişken tanımlıyoruz ve ardından kodları yazıyoruz. os.popen fonksiyonu bize kısaca bir komutu çalıştırmamıza ve çıktısını almamıza yarar. read kısmı ise komutun çıktısını (response) bize string olarak döndürür. index.html'in tekrar kullanıcıya gösterilmesi ve verdiğimiz kodun çıktısını HTML'de görüntüleyebilmek için response diye bir parametre tanımlayıp, result değişkenini ona yazıyorum.
Python:
    command = f"ping  {ip}"
    result = os.popen(command).read()
    return render_template('index.html', response=result)


Kod bir kütüphane olarak eklenmeyip, direkt olarak çalıştırıldıysa kodu çalıştır :
Python:
if __name__ == '__main__':
    app.run(debug=True)

or14aef.png



Zafiyete biraz daha detaylı bakalım




Zafiyet ise tam olarak burada kaynaklanıyor. Herhangi bir karakter filtrelemesi yok. Kodumuz şu an şöyle çalışıyor:


Zafiyetli satır

Python:
result = os.popen(command).read()



nc5o9kn.png


Evet, böyle bir komut çalışıyor ama filtreleme yok. Ya birisi böyle bir şey yaparsa:
Rich (BB code):
ping 8.8.8.8 && dir

Artık "&&" operatörü kullanıldığı için, ilk komut tamamlandıktan sonra dir komutunu çalıştıracak ve zafiyet varsa makinedeki dosyalar görüntülenebilecek.
gan5qpm.png

ctf0sk9.png


umBqNLP.png


Zafiyeti filtrelemek



Burada regex/re denilen bir zımbırtı kullanıyoruz. Yakında ne işe yaradığını anlatacağım ama kısaca bu zımbırtı filtreleme işlemlerinde kullanılıyor. Güzel de bir kütüphanedir, evlenmek gibi bir niyetiniz varsa oldukça delikanlıdır. Kısaca işlevi, gelen input'un bir IP adresi olup olmadığını kontrol etmek.




Python:
from flask import Flask, request, render_template
import subprocess
import re

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/ping', methods=['POST'])
def ping():
    ip = request.form['ip']
    
    if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip):
        return render_template('index.html', response="Geçersiz IP adresi")

    try:
        result = subprocess.check_output(['ping', ip], universal_newlines=True)
    except subprocess.CalledProcessError as e:
        result = f"Hata: {e}"

    return render_template('index.html', response=result)

if __name__ == '__main__':
    app.run(debug=True)
ctf0sk9.png


umBqNLP.png

Eline sağlık
 

narkotix7

Üye
1 Ara 2023
135
58
Mersin
p0qbke2.png



★?


Herkese selamlar, ben Suppressor. Bugün sizlere bilinmeyen ama basit bir açıktan bahsedeceğim. Zafiyetin adı "OS Command Injection" yani komut enjeksiyonu. İlk olarak zafiyeti tanıyalım. Zafiyetin isminden de anlaşılacağı gibi, bir işletim sistemi komutlarını çalıştırmamıza olanak sağlayan bir açıktır. Örneğin Linux'ta (cd, ls) veya Windows'ta (cls, dir) cmd ile ping atmayı deneyelim.


475rn3p.png



Evet, isteğimiz başarılı. Peki, bunu Flask ile bir web sitesi üzerinden yapacak olsak?

İlk olarak gerekli dosya ve klasörleri oluşturalım:
  • app.py
  • templates/index.html


app.py

Python:
from flask import Flask, request, render_template
import os

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/ping', methods=['POST'])
def ping():
    ip = request.form['ip']
    command = f"ping  {ip}"
    result = os.popen(command).read()
    return render_template('index.html', response=result)

if __name__ == '__main__':
    app.run(debug=True)


HTML

HTML:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body{
            background-color: black;
            text-align: center;
        }
        p{
            color:  white;
        }
    </style>
</head>
<body>


    <form action="/ping" method="post">
        <p>IP Address:</p> <input type="text" name="ip">
        <input type="submit" value="Ping">
    </form>
    <br>
    <p>{{ response }}</p>
</body>
</html>



ctf0sk9.png

umBqNLP.png


Kodların açıklanması ve zafiyetin kaynağı:

Önemli bir not: Bu açık PHP'de daha mümkündür ve daha basit bir şekilde gerçekleştirilebilir. Bunun için Python'u biraz zorladım. Örneğin, subprocessi kullanırsanız herhangi bir sorunla karşılaşmazsınız. Ayrıca Python ile böyle bir zafiyetin oluşması oldukça zordur.


Kodları web üzerinden göstermek için Flask, form verilerini almak için requests, bir index göstermek için render_template, sistem komutu çalıştırmak için ise os ekliyoruz.
Python:
from flask import Flask, request, render_template
import os


İlk satırda flask nesnesini tanımlıyoruz arıdann route dekaratörü ile kök dizine bir yani ana url ye bir istek gittiği zaman index.html i return ediyoruz
Python:
app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

Sistemde kodu çalıştırmak için bir eylem olması gerekmekte. Python Flask'te, JavaScript'teki onclick gibi fonksiyonlar olmadığı için /ping diye bir dizine POST request gelirse ilerliyoruz.
Python:
@app.route('/ping', methods=['POST'])

İlgili dizine POST request gelirse, requesti kullanarak form değerlerini çekeceğiz. İstek geldiğinde, IP ismini taşıyan form değerinin verisini alıp, ip isimli bir değişkene tanımlıyoruz.
Python:
def ping():
    ip = request.form['ip']


Ardından command diye bir değişken tanımlayıp, ping attırma işlemini gerçekleştirmek için gerekli satırı tamamlıyoruz. Yani command değeri şu an ping Formdan_gelen_değer şeklinde. İlk olarak result diye bir değişken tanımlıyoruz ve ardından kodları yazıyoruz. os.popen fonksiyonu bize kısaca bir komutu çalıştırmamıza ve çıktısını almamıza yarar. read kısmı ise komutun çıktısını (response) bize string olarak döndürür. index.html'in tekrar kullanıcıya gösterilmesi ve verdiğimiz kodun çıktısını HTML'de görüntüleyebilmek için response diye bir parametre tanımlayıp, result değişkenini ona yazıyorum.
Python:
    command = f"ping  {ip}"
    result = os.popen(command).read()
    return render_template('index.html', response=result)


Kod bir kütüphane olarak eklenmeyip, direkt olarak çalıştırıldıysa kodu çalıştır :
Python:
if __name__ == '__main__':
    app.run(debug=True)

or14aef.png



Zafiyete biraz daha detaylı bakalım




Zafiyet ise tam olarak burada kaynaklanıyor. Herhangi bir karakter filtrelemesi yok. Kodumuz şu an şöyle çalışıyor:


Zafiyetli satır

Python:
result = os.popen(command).read()



nc5o9kn.png


Evet, böyle bir komut çalışıyor ama filtreleme yok. Ya birisi böyle bir şey yaparsa:
Rich (BB code):
ping 8.8.8.8 && dir

Artık "&&" operatörü kullanıldığı için, ilk komut tamamlandıktan sonra dir komutunu çalıştıracak ve zafiyet varsa makinedeki dosyalar görüntülenebilecek.
gan5qpm.png

ctf0sk9.png


umBqNLP.png


Zafiyeti filtrelemek



Burada regex/re denilen bir zımbırtı kullanıyoruz. Yakında ne işe yaradığını anlatacağım ama kısaca bu zımbırtı filtreleme işlemlerinde kullanılıyor. Güzel de bir kütüphanedir, evlenmek gibi bir niyetiniz varsa oldukça delikanlıdır. Kısaca işlevi, gelen input'un bir IP adresi olup olmadığını kontrol etmek.




Python:
from flask import Flask, request, render_template
import subprocess
import re

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/ping', methods=['POST'])
def ping():
    ip = request.form['ip']
    
    if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip):
        return render_template('index.html', response="Geçersiz IP adresi")

    try:
        result = subprocess.check_output(['ping', ip], universal_newlines=True)
    except subprocess.CalledProcessError as e:
        result = f"Hata: {e}"

    return render_template('index.html', response=result)

if __name__ == '__main__':
    app.run(debug=True)
ctf0sk9.png


umBqNLP.png

elinize sağlık, subprocess'de shell=False olduğu sürece zaten zafiyet kalkmıyormu?
 

Suppressor

Request Uzmanı
16 Kas 2022
1,245
790
always, everywhere
Emeğinize sağlık 👏

Teşekkürler.

elinize sağlık, subprocess'de shell=False olduğu sürece zaten zafiyet kalkmıyormu?
Öncelikle teşekkürler. Parametresi tam aklımda değil ama, evet, yanlış hatırlamıyorsam kalkıyordu. Ancak regex kullanmayı tercih ettim.
Elinize saglik,regex kullanmadan engellemek mumkunmu sizce? Mesela eger inputda bosluk varsa demekki ip adresi formatini odemiyor.
Teşekkürler. Evet, mümkün. Üst tarafta da olduğu gibi shell parametresi ile yapılabilir veya gelen inputta "||", "&&" vs. gibi karakterler var mı, bu kontrol edilebilir.
 

Yagami Light0

Katılımcı Üye
5 May 2023
748
353
24
Teşekkürler.


Öncelikle teşekkürler. Parametresi tam aklımda değil ama, evet, yanlış hatırlamıyorsam kalkıyordu. Ancak regex kullanmayı tercih ettim.

Teşekkürler. Evet, mümkün. Üst tarafta da olduğu gibi shell parametresi ile yapılabilir veya gelen inputta "||", "&&" vs. gibi karakterler var mı, bu kontrol edilebilir.
Anladim ,tesekkurler
 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.