The Art of Doing Source Code Review
Hello, Everyone In this digital world, software powers everything, and behind every application lies a source code, but how do we ensure that the code is secure? Enter the world of source code review — a critical practice that goes beyond debugging to uncover hidden vulnerabilities, improve performance, and maintain code quality. In this article, we’ll explore the importance of doing a source code review.
Source code review is the process of thoroughly examining and evaluating the source code of an application to identify any potential security vulnerabilities at the code level. A source code review aims to identify and mitigate any issues that an attacker, such as SQL injection, cross-site scripting, or other types of code-level vulnerabilities, could exploit.
Benefits of doing source code review:
- Identification of Vulnerabilities
- Early Issue Detection
- Cross-Team Learning
- Meeting Compliance Requirements
- Reduced Debugging Costs
Source and Sinks
Source — A source is a point where user-controlled data enters the application.
Sinks — A sink is a point where data is used in a way that can affect the application’s behavior or interact with external systems, potentially leading to vulnerabilities.
Let’s walk through through some code snippets to learn about source code review.
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
@app. route("/read-file", methods= [ "GET" ] )
def read_file():
filename = request.args. get( "filename" )
with open (f" /var/www/uploads/{filename}", "r") as file:
content = file. read ()
return jsonify({"content": content})
if __name__ == "__main__":
app.run(debug=True)
This code is vulnerable to path traversal as the filename parameter is directly concatenated into the file path without validation.
from flask import Flask, request
app = Flask(__name__)
@app.route("/set-config", methods=["POST"])
def set_config():
config = request. json.get "config")
exec (f"settings = {config}")
return "Configuration updated successfully!"
if __name__ == "__main__":
app. run (debug=True)
This code is vulnerable to Remote Code Execution due to using the exec() function with malicious user input as it can be used to execute arbitrary python code.
from flask import Flask, request, redirect, url_for
import logging
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
def is_authenticated_user():
brevity
pass
@app.route('/')
def home():
if not is_authenticated_user():
logging.info('Unauthorized access attempt.')
return redirect(url_for('login'))
redirect_url = request.args.get('redirect_url')
if redirect_url:
logging.info(f'Redirecting to: {redirect_url}')
return redirect(redirect_url)
return 'Welcome'
@app.route('/login')
def login():
return 'Login Page - User authentication goes here.'
if __name__ == '__main__':
app.run(debug=False)
In the above code the redirect_url parameter in the home() route is directly used in the redirect() function without any validation, making it vulnerable to open redirection.
from flask import Flask, request, jsonify
app = Flask(__name__)
USERNAME = "Admin"
PASSWORD = "123456"
@app.route('/')
def home():
return "Welcome to the App!"
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
if username == USERNAME and password == PASSWORD:
return jsonify({"message": "Login successful"}), 200
else:
return jsonify({"message": "Invalid credentials"}), 401
In the above code snippet, the username “Admin” and password “123456” are stored in plaintext in the code.
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
class UserProfileService:
def get_user_profile(self, username):
pass
def update_user_profile(self, user_profile):
pass
user_profile_service = UserProfileService()
@app.route('/edit-profile', methods=['POST'])
def edit_profile():
if not is_authenticated():
return redirect(url_for('login'))
username = request.form.get('username')
user_profile = user_profile_service.get_user_profile(username)
if user_profile.get_username() == username:
user_profile_service.update_user_profile(user_profile)
return redirect(url_for('dashboard'))
@app.route('/dashboard')
def dashboard():
return render_template('dashboard.html')
In the above code snippet, there is no authorization implemented that allows any logged-in user to edit the profile of any other user.
const express = require('express');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const app = express();
const secretKey = process.env.JWT_SECRET;;
const verifyToken = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
const decoded = jwt.decode(token, { complete: true });
req.decoded = decoded.payload;
next();
};
app.get('/admin', verifyToken, (req, res) => {
const { username } = req.decoded;
if (username === 'admin') {
return res.status(200).json({ message: 'Admin access granted' });
}
res.status(403).json({ message: 'Unauthorized access' });
});
app.get('/generate-token', (req, res) => {
const payload = { username: 'user' };
const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
res.status(200).json({ token });
});
In the above code snippet, the JWT signature is not verified, which means the JWT can be altered and change the username from ‘joe’ to ‘admin’.
const express = require('express');
const app = express();
app.get('/search', (req, res) => {
let searchTerm = req.query.term;
searchTerm = searchTerm.replace(/<script>.*?<\/script>/gi, '');
res.send(`<h1>Search results for: ${searchTerm}</h1>`);
});
In the above code snippet, it takes input from the searchterm and puts it directly into the webpage, but it removes only <script> tags but it can be bypassed by sending inputs like <img src = x onerror = alert(1)> making it vulnerable to XSS attack.
from flask import Flask, request, jsonify
app = Flask(__name__)
users = {
"1": {"id": 1, "name": "Alice", "email": "alice@example.com", "is_admin": False},
"2": {"id": 2,"name": "Bob", "email": "bob@example.com", "is_admin": False},
}
@app. route("/update_user", methods=["POST"])
def update_user():
data = request. json
user_id = data. get("id" )
user = users. get(user_id)
if not user:
return jsonify({"error": "User not found"}), 404
user. update(data)
return jsonify({"message": "User updated successfully", "user": user})
In the above code snippet, it is vulnerable to mass assignment vulnerability as the is_admin field can be modified by the user to become the admin user.
We did a manual source code review above, but doing a manual source code review is a very time-consuming and tedious task, so to make it easy I discovered a solution named AquilaX AI.
This website uses AI for doing source code review and making it easy for security engineers and developers to review their source code and find vulnerabilities in it and the best part is that it is free to use and anyone can sign up for it and use it. So, let’s get started
- Navigate to the AquilaX homepage and login into the application.
2. Click on the new scan tab.
3. Enter your GitHub, GitLab, or Bitbucket URL and click on the start-scan button.
4. Observe that it detected 10 vulnerabilities in the code.
There are also some dangerous functions that a security engineer or developer should be aware of while doing source code review.
Dangerous Functions List
Python — eval(), exec(), os.system(), pickle.loads()
Javascript — document.location.href(), document.write()
PHP — unserialize(), eval(), exec(), system(), shell_exec(), include()
Ruby — System(), exec(), yaml.load()
Conclusion
I hope this article provides you with enough information about doing source code review. Initially, start reviewing smaller code snippets, understand the code, and use the combination of manual and automated ways to do source code review.
References