Hands-on Practice
Estimate Time: 8 min read
This lab focuses on input handling mistakes that break a program's trust assumptions and enable unintended file access. You'll work through a small, realistic command-line tool and follow a hands-on workflow to observe how weak validation can let crafted input change program behavior.
The exercises are designed for Programming 1 students learning C++; you will compile and run a simple program, trigger a vulnerability with controlled input, and then implement and verify a focused fix. The tone is practical: run the code, inspect results, and make a minimal corrective change.
Environment Setup
Prepare a basic C++ build environment and a working directory for the exercise. No external libraries are required: a standard C++ compiler such as g++ or clang++ is sufficient and commonly available on Linux, macOS, and Windows (via MinGW or WSL).
# Create a workspace
mkdir librarymate-lab && cd librarymate-lab
# Compile the vulnerable program (example with g++)
# g++ is assumed to be installed; use clang++ if preferred
g++ -std=c++17 -O0 -g -o librarymate vulnerable.cpp
# Compile the exploit helper
g++ -std=c++17 -O0 -g -o print_id exploit.cpp
If your system lacks a compiler, install one with your platform package manager (for example: apt install g++ on Debian/Ubuntu, brew install gcc on macOS, or install MinGW on Windows). Create a records directory and a demonstration file used by the lab:
mkdir -p records
printf "Member: Alice\nBorrowed: 2 items\n" > records/12345.txt
# Put a file outside records to demonstrate unauthorized access
printf "SECRET DATA\n" > ../secrets.txt
Scenario Overview
Kai built a tiny command-line utility called LibraryMate to let library staff look up a member profile by entering a member ID. The program builds a file path by concatenating a base records directory, the supplied ID, and a .txt extension, then opens and displays the file assigned to that ID.
The code trusts that staff will only supply benign IDs and performs only a minimal sanity check before building the path. That implicit trust is the root of the exercise: malformed or crafted IDs can change the resulting path and cause the program to read files outside the intended records directory.
Target Program
The vulnerable application is a compact C++ utility that reads a member ID from stdin, constructs a file path by appending the ID to a base directory and ".txt", and then prints the file contents. The problematic assumption is that any supplied ID is safe to use directly in a filesystem path; the program enforces only a trivial non-empty/length check and does not prevent path traversal or unexpected characters.
Vulnerable Code
#include <iostream>
#include <fstream>
#include <string>
int main() {
const std::string baseDir = "./records/";
std::string memberId;
std::cout << "Enter member ID: ";
std::getline(std::cin, memberId);
// Minimal check: must be non-empty and not too long
if (memberId.empty() || memberId.size() > 64) {
std::cerr << "Invalid ID\n";
return 1;
}
// Construct path by simple concatenation
std::string path = baseDir + memberId + ".txt";
std::ifstream in(path);
if (!in) {
std::cerr << "Could not open record for ID: " << memberId << "\n";
return 1;
}
std::string line;
while (std::getline(in, line)) {
std::cout << line << "\n";
}
return 0;
}
This program demonstrates a single vulnerability class: it uses unvalidated user input when composing a filesystem path. Because the code never constrains allowed characters or canonicalizes the final path, an input that includes traversal segments (for example, ".." or directory separators) can point the program at files outside ./records.
Exploit Program
The exploit helper is a minimal C++ program that writes a crafted ID to stdout so you can pipe it into the vulnerable program. The objective is to demonstrate that supplying a traversal-style ID can cause LibraryMate to open a file outside the records directory, exposing data the program was never intended to reveal.
Exploit Code
#include <iostream>
#include <string>
int main() {
// A crafted ID that moves up one directory and targets a file outside records
std::string crafted = "../secrets"; // when appended becomes ./records/../secrets.txt
std::cout << crafted << "\n";
return 0;
}
Run the vulnerable program with this helper by piping its output: ./print_id | ./librarymate. If a file such as ../secrets.txt exists, the vulnerable program will open and print it, showing that a malformed ID can change the resolved path and bypass the intended directory constraint.
Practice Objectives
Task 1: Trigger the vulnerability
In this task, you will run the vulnerable program and provide a traversal-style ID to observe the unintended file read. Your objective is to reproduce the unauthorized read by piping the exploit helper into the target and noting the file contents printed by the program. After completing this task you should be able to demonstrate how a crafted ID leads the program to open a file outside the records directory.
Task 2: Analyze the vulnerable behavior
In this task, you will inspect the vulnerable source to find where the trust assumption is made and where the input is used without sufficient validation. Your objective is to identify the line that concatenates baseDir, memberId, and the extension, and to add a temporary diagnostic print of the final path so you can observe exactly what path the program attempts to open. After completing this task you will understand how the input maps to an actual filesystem path.
Task 3: Implement a focused mitigation
In this task, you will modify the program to enforce a strict input policy for member IDs. Your objective is to reject IDs containing directory separators or dot-segments and to accept only a constrained set of characters (for example: digits only). A small validation example that checks each character is illustrative guidance:
// contextual snippet: reject any non-digit characters
for (char c : memberId) {
if (!std::isdigit(static_cast<unsigned char>(c))) {
std::cerr << "Invalid ID characters\n";
return 1;
}
}
After completing this task the program should refuse traversal-style IDs and only open files inside the intended records directory when valid numeric IDs are supplied.
Task 4: Verify the mitigation
In this task, you will re-run the exploit input against your patched program to confirm the fix. Your objective is to demonstrate that the same piped ID which previously caused the program to open ../secrets.txt is now rejected, and that valid IDs (for example, 12345) still open the correct records file. After completing this task you will have validated that the implemented input validation restores the original trust assumptions and prevents simple path-traversal attacks.