Python Projeleri İçin Açık Kaynak Araçlarla DevSecOps Pipeline Oluşturma

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.

alt text

Ş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

Written by

Furkan Turan