Skip to content

Commit 68c9bd2

Browse files
authored
Merge pull request #235 from AikidoSec/decode-url-strings
Keep both encoded and unencoded http data
2 parents fe03566 + 7acf63e commit 68c9bd2

File tree

3 files changed

+33
-17
lines changed

3 files changed

+33
-17
lines changed

Aikido.Zen.Core/Helpers/HttpHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ public static async Task<HttpDataResult> ReadAndFlattenHttpDataAsync(
8484
LogHelper.ErrorLog(Agent.Logger, $"caught error while parsing body: {e.Message}");
8585
}
8686

87-
// Decode percent-encoded values
88-
UserInputHelper.DecodeUriValues(result);
87+
// Add decoded URI strings to result dictionary where applicable
88+
UserInputHelper.ProcessUriValues(result);
8989

9090
return new HttpDataResult
9191
{

Aikido.Zen.Core/Helpers/UserInputHelper.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ public static class UserInputHelper
1414
private const int MaxDecodeUriPasses = 2;
1515

1616
/// <summary>
17-
/// Decodes percent-encoded values in place (e.g. who%61mi => whoami).
17+
/// Processes all values and adds URI decoded variants where applicable (e.g. who%61mi => whoami).
1818
/// </summary>
19-
/// <param name="values">Dictionary containing user input values.</param>
20-
public static void DecodeUriValues(IDictionary<string, string> values)
19+
/// <param name="result">The dictionary to store processed data.</param>
20+
public static void ProcessUriValues(IDictionary<string, string> values)
2121
{
2222
if (values == null || values.Count == 0)
2323
{
@@ -26,7 +26,11 @@ public static void DecodeUriValues(IDictionary<string, string> values)
2626

2727
foreach (var key in values.Keys.ToList())
2828
{
29-
values[key] = DecodeUriComponent(values[key]);
29+
string original = values[key];
30+
if (TryDecodeUriComponent(original, out string decoded))
31+
{
32+
values[$"{key}|decoded"] = decoded;
33+
}
3034
}
3135
}
3236

@@ -128,13 +132,14 @@ public static bool IsMultipart(string contentType, out string boundary)
128132
return isMultipart;
129133
}
130134

131-
private static string DecodeUriComponent(string input)
135+
private static bool TryDecodeUriComponent(string input, out string decoded)
132136
{
133-
string decoded = input;
137+
bool changed = false;
138+
decoded = input;
134139

135140
if (string.IsNullOrEmpty(input))
136141
{
137-
return decoded;
142+
return false;
138143
}
139144

140145
for (int i = 0; i < MaxDecodeUriPasses; i++)
@@ -146,9 +151,10 @@ private static string DecodeUriComponent(string input)
146151
}
147152

148153
decoded = next;
154+
changed = true;
149155
}
150156

151-
return decoded;
157+
return changed;
152158
}
153159
}
154160
}

Aikido.Zen.Test/HttpHelperTests.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,25 @@ public async Task ReadAndFlattenHttpDataAsync_ShouldDecodePercentEncodedUserInpu
131131
bodyStream.Length);
132132

133133
// Assert
134-
Assert.That(result.FlattenedData["route.command"], Is.EqualTo("whoami"));
135-
Assert.That(result.FlattenedData["query.path"], Is.EqualTo("../etc/passwd"));
136-
Assert.That(result.FlattenedData["query.emoji"], Is.EqualTo("\U0001F600"));
137-
Assert.That(result.FlattenedData["query.double"], Is.EqualTo("whoami"));
134+
Assert.That(result.FlattenedData["route.command"], Is.EqualTo("who%61mi"));
135+
Assert.That(result.FlattenedData["query.path"], Is.EqualTo("%2e%2e%2fetc%2fpasswd"));
136+
Assert.That(result.FlattenedData["query.emoji"], Is.EqualTo("%F0%9F%98%80"));
137+
Assert.That(result.FlattenedData["query.double"], Is.EqualTo("%2577%2568%256f%2561%256d%2569"));
138138
Assert.That(result.FlattenedData["query.invalid"], Is.EqualTo("%E0%A4%A"));
139-
Assert.That(result.FlattenedData["headers.X-Custom"], Is.EqualTo("a+b+c"));
140-
Assert.That(result.FlattenedData["cookies.session"], Is.EqualTo("abc123"));
141-
Assert.That(result.FlattenedData["body.cmd"], Is.EqualTo("whoami"));
139+
Assert.That(result.FlattenedData["headers.X-Custom"], Is.EqualTo("a+b%2Bc"));
140+
Assert.That(result.FlattenedData["cookies.session"], Is.EqualTo("abc%31%32%33"));
141+
Assert.That(result.FlattenedData["body.cmd"], Is.EqualTo("who%61mi"));
142142
Assert.That(result.FlattenedData["body.literal"], Is.EqualTo("a+b"));
143+
144+
Assert.That(result.FlattenedData["route.command|decoded"], Is.EqualTo("whoami"));
145+
Assert.That(result.FlattenedData["query.path|decoded"], Is.EqualTo("../etc/passwd"));
146+
Assert.That(result.FlattenedData["query.emoji|decoded"], Is.EqualTo("\U0001F600"));
147+
Assert.That(result.FlattenedData["query.double|decoded"], Is.EqualTo("whoami"));
148+
Assert.That(result.FlattenedData["headers.X-Custom|decoded"], Is.EqualTo("a+b+c"));
149+
Assert.That(result.FlattenedData["cookies.session|decoded"], Is.EqualTo("abc123"));
150+
Assert.That(result.FlattenedData["body.cmd|decoded"], Is.EqualTo("whoami"));
151+
Assert.That(result.FlattenedData.ContainsKey("query.invalid|decoded"), Is.False);
152+
Assert.That(result.FlattenedData.ContainsKey("body.literal|decoded"), Is.False);
143153
}
144154

145155
[Test]

0 commit comments

Comments
 (0)