Bu makalede, Python projelerinde güvenliği sağlamak için açık kaynaklı araçlarla nasıl etkili bir DevSecOps pipeline oluşturabileceğinizi inceleyeceğiz. Açık kaynak araçlar, ticari araçlar kadar kapsamlı olmasalar da, geniş bir topluluk tarafından sürekli olarak geliştirildikleri için, güvenlik açıklarını belirli bir seviyede tespit edebilir ve projenizde temel bir güvenlik standardı oluşturmanıza yardımcı olabilirler.
Bandit, Safety ve Trivy araçlarını kullanarak, bir Gitlab pipeline oluşturacağız. Elimizde, Flask Framework’ü kullanılarak yazılmış küçük bir kod var.
import os
import sqlite3
import flask
from flask import request, session, redirect, url_for, render_template
app = flask.Flask(__name__)
app.secret_key = 'supersecretkey' # Hardcoded secret key (zafiyetli)
DATABASE = 'database.db'
def get_db():
conn = sqlite3.connect(DATABASE)
return conn
@app.route('/')
def index():
if 'username' in session:
return redirect(url_for('profile'))
return render_template('index.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
# SQL Injection zafiyeti
conn = get_db()
conn.execute(f"INSERT INTO users (username, password) VALUES ('{username}', '{password}')")
conn.commit()
return redirect(url_for('index'))
return render_template('register.html')
@app.route('/profile')
def profile():
if 'username' in session:
username = session['username']
# Command injection zafiyeti
command = f'ls /home/{username}/files'
files = os.popen(command).read() # Komut sonucu direkt işleniyor
return render_template('profile.html', files=files)
return redirect(url_for('index'))
if __name__ == "__main__":
# Veritabanını oluşturma
conn = sqlite3.connect(DATABASE)
conn.execute('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)')
conn.close()
app.run(debug=True) # Debug modu aktif (zafiyetli)
Kodda, injection türünde zafiyetler ve konfigürasyon problemleri var. Bu zafiyetleri tespit etmek için bir SAST aracına ihtiyaç duyuyoruz. Bandit aracını, bu amaçla kullanabiliriz.
bandit_scan:
stage: sast_scan
image: python:3.11
script:
- pip install bandit
- bandit -r app.py
allow_failure: true
Yukarıdaki basit konfigürasyonla taramayı gerçekleştirebiliriz. Şimdi sonucu inceleyelim.
$ bandit -r app.py
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.11.9
Run started:2024-08-15 16:21:22.794536
Test results:
>> Issue: [B105:hardcoded_password_string] Possible hardcoded password: 'supersecretkey'
Severity: Low Confidence: Medium
CWE: CWE-259 (https://cwe.mitre.org/data/definitions/259.html)
More Info: https://bandit.readthedocs.io/en/1.7.9/plugins/b105_hardcoded_password_string.html
Location: ./app.py:7:17
6 app = flask.Flask(__name__)
7 app.secret_key = 'supersecretkey' # Hardcoded secret key (zafiyetli)
8
--------------------------------------------------
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
Severity: Medium Confidence: Medium
CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
More Info: https://bandit.readthedocs.io/en/1.7.9/plugins/b608_hardcoded_sql_expressions.html
Location: ./app.py:29:21
28 conn = get_db()
29 conn.execute(f"INSERT INTO users (username, password) VALUES ('{username}', '{password}')")
30 conn.commit()
--------------------------------------------------
>> Issue: [B605:start_process_with_a_shell] Starting a process with a shell, possible injection detected, security issue.
Severity: High Confidence: High
CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
More Info: https://bandit.readthedocs.io/en/1.7.9/plugins/b605_start_process_with_a_shell.html
Location: ./app.py:42:16
41 command = f'ls /home/{username}/files'
42 files = os.popen(command).read() # Komut sonucu direkt işleniyor
43 return render_template('profile.html', files=files)
--------------------------------------------------
>> Issue: [B201:flask_debug_true] A Flask app appears to be run with debug=True, which exposes the Werkzeug debugger and allows the execution of arbitrary code.
Severity: High Confidence: Medium
CWE: CWE-94 (https://cwe.mitre.org/data/definitions/94.html)
More Info: https://bandit.readthedocs.io/en/1.7.9/plugins/b201_flask_debug_true.html
Location: ./app.py:52:4
51
52 app.run(debug=True) # Debug modu aktif (zafiyetli)
--------------------------------------------------
Code scanned:
Total lines of code: 38
Total lines skipped (#nosec): 0
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
Run metrics:
Total issues (by severity):
Undefined: 0
Low: 1
Medium: 1
High: 2
Total issues (by confidence):
Undefined: 0
Low: 0
Medium: 3
High: 1
Files skipped (0):
Koddaki SQL ve OS injection zafiyetlerini tespit edebildi. Bir konfigürasyon dosyasından çekilmesi gereken secret_key
, hardcoded secret olarak bulundu. Bu tarz zafiyetlerin bulanabilmesi için özel olarak geliştirilmiş secret tarama araçları da kullanılabilir. Son olarak projenin debug modunda çalıştırılacağını da raporlayabildi.
safety_scan:
stage: dependency_scan
image: python:3.11
script:
- pip install safety
- safety check -r requirements.txt
allow_failure: true
Safety, Python projelerinde kullanılan kütüphanelerde, önceden bulunmuş bir açık olup olmadığını tespit etmemizi sağlayan bir SCA aracı. Requirements.txt dosyasındaki kütüphaneleri, açık kaynak veritabanlarından sorgulayıp, güvenlik problemlerini raporlayabiliyor. Requirements.txt dosyasının içeriği aşağıdaki gibi:
flask==1.1.2
requests==2.22.0
Şimdi sonuçları inceleyelim.
$ safety check -r requirements.txt
Safety v3.2.5 is scanning for Vulnerabilities...
Scanning dependencies in your files:
-> requirements.txt
Using open-source vulnerability database
Found and scanned 2 packages
Timestamp 2024-08-15 16:22:03
3 vulnerabilities reported
0 vulnerabilities ignored
+==============================================================================+
VULNERABILITIES REPORTED
+==============================================================================+
-> Vulnerability found in requests version 2.22.0
Vulnerability ID: 58755
Affected spec: >=2.3.0,<2.31.0
ADVISORY: Requests 2.31.0 includes a fix for CVE-2023-32681: Since
Requests 2.3.0, Requests has been leaking Proxy-Authorization headers...
CVE-2023-32681
For more information about this vulnerability, visit
https://data.safetycli.com/v/58755/97c
To ignore this vulnerability, use PyUp vulnerability id 58755 in safety’s
ignore command-line argument or add the ignore to your safety policy file.
-> Vulnerability found in requests version 2.22.0
Vulnerability ID: 71064
Affected spec: <2.32.2
ADVISORY: Affected versions of Requests, when making requests
through a Requests `Session`, if the first request is made with...
CVE-2024-35195
For more information about this vulnerability, visit
https://data.safetycli.com/v/71064/97c
To ignore this vulnerability, use PyUp vulnerability id 71064 in safety’s
ignore command-line argument or add the ignore to your safety policy file.
-> Vulnerability found in flask version 1.1.2
Vulnerability ID: 55261
Affected spec: <2.2.5
ADVISORY: Flask 2.2.5 and 2.3.2 include a fix for CVE-2023-30861:
When all of the following conditions are met, a response containing...
CVE-2023-30861
For more information about this vulnerability, visit
https://data.safetycli.com/v/55261/97c
To ignore this vulnerability, use PyUp vulnerability id 55261 in safety’s
ignore command-line argument or add the ignore to your safety policy file.
Requests ve flask kütüphanelerindeki zafiyetleri tespit edebildi. Şimdi uygulamayı konteynırlaştırma aşamasına geçebiliriz. İlk önce bu projeyle bir imaj oluşturalım.
container_build:
image: docker:latest
stage: container_build
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
İmajı build edip, Gitlab Registry’sine gönderdik. Şimdi açık kaynak bir araç olan Trivy ile oluşturduğumuz koyteynırı tarayalım.
container_scan:
stage: container_scan
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker pull ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
script:
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image --severity HIGH,CRITICAL --exit-code 1 ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
allow_failure: true
Trivy aracının da konteyner versiyonunu kullanarak, registry’den projenin imajını çekip tarama gerçekleştirebiliriz.
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image --severity HIGH,CRITICAL --exit-code 1 ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
Python kütüphanelerindeki zafiyetlerin yanısıra, çıktılarda konteynır imajının içinde bulunan kütüphanelerdeki güvenlik problemlerini de görebiliyoruz.
Şimdi oluşturduğumuz bütün adımları birleştirip pipeline’ı oluşturalım.
stages:
- sast_scan
- dependency_scan
- container_build
- container_scan
bandit_scan:
stage: sast_scan
image: python:3.11
script:
- pip install bandit
- bandit -r app.py
allow_failure: true
safety_scan:
stage: dependency_scan
image: python:3.11
script:
- pip install safety
- safety check -r requirements.txt
allow_failure: true
container_build:
image: docker:latest
stage: container_build
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
container_scan:
stage: container_scan
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker pull ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
script:
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image --severity HIGH,CRITICAL --exit-code 1 ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
allow_failure: true
□