Breaking Bots: Investigating SQL Injection via AWS Lex Chatbots

kangwijen

kangwijen

4 min read
Breaking Bots: Investigating SQL Injection via AWS Lex Chatbots

An Overview

I found this pattern while building a challenge for ITSEC CTF 2025. I wanted players to exploit a realistic cloud-native attack path, not just another web form. After reading MorattiSec's write-up on red team persistence through AWS Lex chatbots The crow flies at midnight, I started looking at Lex as a legitimate attack surface rather than an abstraction layer that removes classic web vulnerabilities.

The challenge is a meeting booking chatbot backed by a relational database. The bot collects a few inputs from the user such as: meeting topic, time, and email. The injection vector is the meeting topic slot, which is typed as AMAZON.FreeFormInput. Amazon Lex V2 does not execute SQL. The vulnerability exists entirely in how the Lambda fulfillment function processes that slot value.

The Challenge Setup

How Lex V2 Processes Input

Lex V2 classifies user messages into intents (the action the user wants to perform) and extracts slots (the specific data values needed to fulfill that intent). After slots are resolved, Lex invokes an AWS Lambda function and passes the session state, including all slot values, in a structured JSON event. Most built-in slot types apply normalization. AMAZON.Date converts natural language like next Monday into an ISO date. AMAZON.Number converts five into 5. This normalization creates a natural expectation that Lex is cleaning input before it reaches your backend.

AMAZON.FreeFormInput breaks that expectation entirely. AWS documents it as follows:

AMAZON.FreeFormInput can be used to capture free form input as-is from the end user. The resolved value is the entire input utterance.

This means whatever the user types is forwarded as-is to Lambda with no transformation, no filtering, and no character restriction.

Request Flow

mermaid

Slot Type Comparison

Slot Type

Example User Input

Lex Output Behavior

Security Impact

AMAZON.Date

next Monday

Normalized to a standard date format (for example 2025-06-01)

Lower parsing ambiguity in backend logic

AMAZON.Number

forty two

Normalized to numeric value (for example 42)

Lower type confusion risk

AMAZON.FreeFormInput

' OR '1'='1

Passed through as-is, full utterance unchanged

High risk if concatenated into SQL

The Vulnerable Lambda Code

The Lambda fulfillment handler reads the topic slot value from the Lex event and concatenates it directly into a SQL query string.

python
slots = event["sessionState"]["intent"]["slots"]
topic = slots["Topic"]["value"]["interpretedValue"]
time = slots["Time"]["value"]["interpretedValue"]
email = slots["Email"]["value"]["interpretedValue"]

# VULNERABLE: user-controlled values are concatenated into SQL
query = f"INSERT INTO bookings (topic, time, email) VALUES ('{topic}', '{time}', '{email}')"
cursor.execute(query)

When a normal user inputs Team sync, the query becomes:

sql
INSERT INTO bookings (topic, time, email) VALUES ('Team sync', '10:00', 'user@example.com')

When an attacker inputs ' OR '1'='1 as the topic, the query becomes:

sql
INSERT INTO bookings (topic, time, email) VALUES ('' OR '1'='1', '10:00', 'user@example.com')

Exploitation

The first signal during testing was the raw MySQL error being returned by the bot after I entered a topic starting with a single quote. The response exposed:

sql
Error: (1064, "You have an error in your SQL syntax ...')

This confirms two issues at once. User input was reaching SQL construction unmodified, and backend database errors were being leaked directly to the chat UI.

Before the SQLi verification step, I also tested a one-liner fuzz payload for multiple bug classes such as SQLi, XSS, command injection, and SSRF. Most fields were handled better than expected. Date, time, and email rejected malformed values. Recipient name and meeting topic were more permissive, which made them the strongest candidates for deeper SQLi testing.

From there, an error-based SQLi payload path confirmed the backend database behavior. In the challenge environment, this progressed to schema enumeration and targeted extraction because the vulnerable query path was reachable through the permissive slot and the SQL error was reflected in the bot response.

The Fix

Use the Correct Slot Type First

Use strict built-in or custom slot types whenever possible. In this challenge, time and email should use constrained slot types, while only truly open-ended fields should use AMAZON.FreeFormInput. Reducing free-form capture narrows the injection surface before the payload reaches Lambda.

Use Parameterized Queries for All Database Writes

The correct fix is to bind slot values as parameters rather than interpolating them into the query string. With parameterized queries, the database engine treats the value as data regardless of its content.

python
# SAFE: slot value is bound as a parameter, not concatenated
query = "INSERT INTO bookings (topic, time, email) VALUES (%s, %s, %s)"
cursor.execute(query, (topic, time, email))

With this in place, inputting ' OR '1'='1 as topic produces the literal string ' OR '1'='1 stored in the database column, not an altered query structure.

Final Thoughts

The vulnerability in this challenge exists because AMAZON.FreeFormInput passes user text to Lambda as-is and the Lambda function constructs SQL through string concatenation. Neither Lex nor Lambda prevents this on its own.

If you want the full exploitation chain for the challenge, including payload progression and the exact flag recovery flow, read the official writeup here: ITSEC Asia Summit 2025 Cloud Challenge: rabbit-hole

References