Skip to content

Commit

Permalink
Merge pull request #5032 from thc202/replacer/host-header
Browse files Browse the repository at this point in the history
replacer: allow to replace the Host header
  • Loading branch information
kingthorin authored Oct 24, 2023
2 parents f546829 + 8312cdb commit 95b0e38
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 1 deletion.
3 changes: 2 additions & 1 deletion addOns/replacer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ All notable changes to this add-on will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

### Changed
- Allow to replace (change or remove) the Host header (Issue 5475).

## [15] - 2023-10-12
### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.network.HttpHeaderField;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpRequestHeader;
Expand Down Expand Up @@ -222,7 +226,9 @@ static List<String> parseReplacementTokens(String data) {
}

@Override
@SuppressWarnings("unchecked")
public void onHttpRequestSend(HttpMessage msg, int initiator, HttpSender httpSender) {
boolean hostHeaderChanged = false;
for (ReplacerParamRule rule : this.getParams().getRules()) {
if (rule.isEnabled()
&& rule.appliesToInitiator(initiator)
Expand All @@ -237,6 +243,8 @@ public void onHttpRequestSend(HttpMessage msg, int initiator, HttpSender httpSen
"Add in request header: {} : {}",
rule.getMatchString(),
rule.getReplacement());
hostHeaderChanged |=
HttpRequestHeader.HOST.equalsIgnoreCase(rule.getMatchString());
if (rule.getReplacement().length() == 0) {
// Remove the header
msg.getRequestHeader().setHeader(rule.getMatchString(), null);
Expand All @@ -260,7 +268,9 @@ public void onHttpRequestSend(HttpMessage msg, int initiator, HttpSender httpSen
rule.getEscapedReplacement(),
rule.isTokenProcessingEnabled());
try {
List<HttpHeaderField> oldHostHeaders = getHostHeaders(msg);
msg.setRequestHeader(new HttpRequestHeader(header));
hostHeaderChanged |= !oldHostHeaders.equals(getHostHeaders(msg));
} catch (HttpMalformedHeaderException e) {
LOGGER.error(e.getMessage(), e);
}
Expand Down Expand Up @@ -293,6 +303,24 @@ public void onHttpRequestSend(HttpMessage msg, int initiator, HttpSender httpSen
}
}
}

if (hostHeaderChanged) {
Map<String, Object> properties;
if (msg.getUserObject() instanceof Map<?, ?>) {
properties = (Map<String, Object>) msg.getUserObject();
} else {
properties = new HashMap<>();
msg.setUserObject(properties);
}

properties.put("host.normalization", Boolean.FALSE);
}
}

private static List<HttpHeaderField> getHostHeaders(HttpMessage msg) {
return msg.getRequestHeader().getHeaders().stream()
.filter(e -> HttpRequestHeader.HOST.equalsIgnoreCase(e.getName()))
.collect(Collectors.toList());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ <h4>Request Header String</h4>
In this case the 'Match String' will be treated as a string or regex expression.
If it is present in the request header then it will be replaced by the replacement text.

<h5>Host Header</h5>
Both the Request Header and Request Header String match types can change/remove the Host header, while sending the message to the original request-target.

<h4>Request Body String</h4>
In this case the 'Match String' will be treated as a string or regex expression.
If it is present in the request body then it will be replaced by the replacement text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesRegex;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.zaproxy.zap.extension.replacer.ReplacerParamRule.MatchType.REQ_BODY_STR;
import static org.zaproxy.zap.extension.replacer.ReplacerParamRule.MatchType.REQ_HEADER;
Expand All @@ -31,6 +34,7 @@
import static org.zaproxy.zap.extension.replacer.ReplacerParamRule.MatchType.RESP_HEADER;
import static org.zaproxy.zap.extension.replacer.ReplacerParamRule.MatchType.RESP_HEADER_STR;

import java.util.Map;
import java.util.regex.PatternSyntaxException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -40,6 +44,7 @@
import org.junit.jupiter.params.provider.ValueSource;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpRequestHeader;

class ExtensionReplacerTest {

Expand All @@ -49,10 +54,14 @@ class ExtensionReplacerTest {
new String(new byte[] {'a', 'b', 'c', 1, 2, 3, 'd', 'e', 'f'}, US_ASCII);
private HttpMessage msg;

private ExtensionReplacer extensionReplacer;

@BeforeEach
void setUp() throws Exception {
msg = new HttpMessage();
msg.setRequestHeader("GET https://example.com/ HTTP/1.1");

extensionReplacer = new ExtensionReplacer();
}

@ParameterizedTest
Expand Down Expand Up @@ -115,6 +124,88 @@ void shouldMatchUrlRegex(String targetUrl, boolean expectedMatch) {
assertThat(matches, equalTo(expectedMatch));
}

@Test
void shouldReplaceHostHeaderInRequest() throws HttpMalformedHeaderException {
// Given
msg.setRequestHeader("GET / HTTP/1.1\r\n" + hostHeader("x"));
String replacement = "y";
replacerRule(REQ_HEADER, HttpRequestHeader.HOST, replacement);

// When
extensionReplacer.onHttpRequestSend(msg, 0, null);

// Then
assertThat(
msg.getRequestHeader().getHeader(HttpRequestHeader.HOST), is(equalTo(replacement)));
assertHostNormalizationDisabled(msg);
}

private void replacerRule(
ReplacerParamRule.MatchType matchType, String matchString, String replacement) {
extensionReplacer
.getParams()
.getRules()
.add(
new ReplacerParamRule(
"", matchType, matchString, false, replacement, null, true));
}

private static String hostHeader(String value) {
return HttpRequestHeader.HOST + ": " + value;
}

private static void assertHostNormalizationDisabled(HttpMessage msg) {
@SuppressWarnings("unchecked")
Map<String, Object> userObject = (Map<String, Object>) msg.getUserObject();
assertThat(userObject, hasEntry("host.normalization", Boolean.FALSE));
}

@Test
void shouldRemoveHostHeaderInRequest() throws HttpMalformedHeaderException {
// Given
msg.setRequestHeader("GET / HTTP/1.1\r\n" + hostHeader("x"));
replacerRule(REQ_HEADER, HttpRequestHeader.HOST, "");

// When
extensionReplacer.onHttpRequestSend(msg, 0, null);

// Then
assertThat(msg.getRequestHeader().getHeader(HttpRequestHeader.HOST), is(nullValue()));
assertHostNormalizationDisabled(msg);
}

@Test
void shouldReplaceHostHeaderInRequestHeader() throws HttpMalformedHeaderException {
// Given
String hostHeader = hostHeader("x");
msg.setRequestHeader("GET / HTTP/1.1\r\n" + hostHeader);
String replacement = "y";
replacerRule(REQ_HEADER_STR, hostHeader, hostHeader(replacement));

// When
extensionReplacer.onHttpRequestSend(msg, 0, null);

// Then
assertThat(
msg.getRequestHeader().getHeader(HttpRequestHeader.HOST), is(equalTo(replacement)));
assertHostNormalizationDisabled(msg);
}

@Test
void shouldRemoveHostHeaderInRequestHeader() throws HttpMalformedHeaderException {
// Given
String hostHeader = hostHeader("x");
msg.setRequestHeader("GET / HTTP/1.1\r\n" + hostHeader);
replacerRule(REQ_HEADER_STR, hostHeader, "");

// When
extensionReplacer.onHttpRequestSend(msg, 0, null);

// Then
assertThat(msg.getRequestHeader().getHeader(HttpRequestHeader.HOST), is(nullValue()));
assertHostNormalizationDisabled(msg);
}

@Test
void shouldAddHeaderByHexValueInRequest() throws HttpMalformedHeaderException {
// Given
Expand Down

0 comments on commit 95b0e38

Please sign in to comment.