/*以流的形式获取请求参数*/

本文介绍了一种通过ServletInputStream从HTTP请求中读取原始字节流的方法,并展示了如何使用这种方式处理POST请求中的数据。

RequestDemo5.java文件

package com.neu;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*以流的形式获取请求参数*/
//一般情况下不使用,文件上传时有时用
public class RequestDemo5 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        ServletInputStream in = request.getInputStream();
        int len = -1;
        byte b[] = new byte[1024];
        while((len=in.read(b)) != -1){
            System.out.println(new String(b,0,len));
        }
        in.close();
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        doGet(request, response);
    }

}

index.jsp文件:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
  <head>
    <title>注册页面</title>
  </head>

 <body>
    <form action="/Request/servlet/RequestDemo4" method="post">
        用户名:<input type="text" name="username"/><br/>
        密码:<input type="password" name="password"/><br/>
        确认密码:<input type="password" name="password"/><br/>
        <input type="submit" value="注册"/>
    </form>
  </body>

</html>

运行:
http://localhost:8080/Request/
运行结果:
封装前:User [username=null, password=null, gender=null]
封装后:User [username=ttt, password=[444, 666], gender=null]

sm4HexKey := "xxx" sm4keyBytes := codec.DecodeHex(sm4HexKey)~ // 将十六进制密钥转为字节数组 // hijackHTTPRequest 会在过滤后的请求到达Yakit MITM前被调用,可以通过该函数提前将请求修改或丢弃 // isHttps 请求是否为https请求 // url 网站URL // req 请求 // forward(req) 提交修改后的请求,如果未被调用,则使用原始的请求 // drop() 丢弃请求 hijackHTTPRequest = func(isHttps, url, req, forward /*func(modifiedRequest []byte)*/, drop /*func()*/) { println("oringinReq:",req) // 2. 提取并解析请求体 body := poc.GetHTTPPacketBody(req) println("原始请求体:", string(body)) if body !=nil && body.StartsWith( "request=") { // 3. 提取密文数据(去除"request="前缀) encryptedHex := string(body)[len("request="):] // 4. Base64解码(网络传输常用编码) encryptedBytes, err := codec.DecodeHex(encryptedHex) if err != nil { println("Hex解码失败:", err.Error()) return } // 5. SM4 ECB解密 decrypted, err := codec.Sm4ECBDecryptWithPKCSPadding(sm4keyBytes, encryptedBytes, nil) if err != nil { println("SM4解密失败:", err.Error()) return } println("解密后数据:", string(decrypted)) // 6. 重构请求 modifiedReq := poc.ReplaceHTTPPacketBody(req, decrypted) // println("modifiedReq:",modifiedReq) forward(modifiedReq) // 提交修改后的请求 } else { forward(req) // 无需修改直接转发 } } beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){ req_method = poc.GetHTTPRequestMethod(req /*type: []byte*/) url = poc.GetUrlFromHTTPRequest("https" /*type: string*/, req /*type: []byte*/) req_body = poc.GetHTTPPacketBody(req) //req_body返回的是byte类型,codec的sm4加密函数需要传入string,加密完再转为hex println("tt:",json.dumps(req_body)) if req_method == "POST" && url.StartsWith("Example Domain") && req_body != nil { // 加密 encryptedvalue = codec.Sm4ECBEncryptWithPKCSPadding(sm4keyBytes /*type: []byte*/, string(req_body), nil /*type: []byte*/)~ encryptedHex = codec.EncodeToHex(encryptedvalue) encryptedBytes = "request=" + string(encryptedHex) req = poc.ReplaceBody(req /*type: []byte*/, []byte(encryptedBytes) /*type: []byte*/, false /*type: bool*/) return []byte(req) } } yakit热加载再增加了beforeRequest方法后,服务端返回500,不使用beforeRequest返回200,哪里出错了
最新发布
09-11
<think> 我们分析提供的代码,发现有两个主要函数:`hijackHTTPRequest` 和 `beforeRequest`。问题描述是:当增加了`beforeRequest`方法后,服务端返回500错误,不使用`beforeRequest`则返回200。我们需要找出`beforeRequest`函数中的错误。 首先,我们看一下`beforeRequest`函数的逻辑: 1. 获取请求方法(`req_method`)、URL(`url`)和请求体(`req_body`)。 2. 如果是POST请求,并且URL以"Example Domain"开头,并且请求体不为空,则进行加密操作。 3. 加密过程:使用SM4 ECB加密(带PKCS填充)对请求体字符串加密,然后转换为十六进制字符串,再拼接上"request="前缀。 4. 替换原始请求请求体为这个新的加密后的字符串,并返回修改后的请求。 可能的问题点: 1. 在加密前,将`req_body`(byte切片)转成了字符串,这可能导致二进制数据被错误转换(比如包含非UTF-8字符)。但根据代码,`req_body`是从HTTP请求体中获取的,可能是文本数据,也可能是二进制。如果请求体是二进制(比如图片、已加密数据等),转字符串会破坏数据。然而,在上下文中,我们之前解密时看到的是文本(因为解密后是明文),所以这里假设请求体是文本。 2. 加密函数`codec.Sm4ECBEncryptWithPKCSPadding`要求第二个参数是`string`,所以转换是必要的。 3. 加密后的数据是字节数组,我们使用`codec.EncodeToHex`将其转换为十六进制字符串,然后拼上"request="。这一步看起来没问题。 4. 替换请求体:使用`poc.ReplaceBody`函数,将请求体替换为新的字节数组(即"request="+十六进制字符串的字节形式)。 5. 最后,返回修改后的请求(`return []byte(req)`)。这里需要注意,`poc.ReplaceBody`的返回值是修改后的请求(字节数组),而函数签名中`beforeRequest`应该返回修改后的请求(字节数组)。但是,代码中写的是`return []byte(req)`,这里`req`已经是字节数组,再转换一次?而且,前面`req`被重新赋值了,但赋值语句是`req = poc.ReplaceBody(...)`,而`poc.ReplaceBody`返回的是`[]byte`,所以这里`req`已经是字节数组,不需要再转换。 但是,观察代码: ```go req = poc.ReplaceBody(req /*type: []byte*/, []byte(encryptedBytes) /*type: []byte*/, false /*type: bool*/) return []byte(req) ``` 这里有两个问题: - `ReplaceBody`返回的是修改后的整个请求(字节数组),然后赋值给`req`。因为`req`本身是字节数组,所以`req`现在已经是修改后的请求。 - 然后,我们返回`[]byte(req)`,这相当于又把`req`(已经是字节数组)转成字节数组,这是多余的,而且语法上也是错误的,因为`req`是`[]byte`,而`[]byte(req)`相当于`[]byte`类型转换,但`req`已经是`[]byte`,所以这个转换没有改变,但是多此一举。 但是,这并不会导致错误,因为转换前后是相同的。 更严重的问题可能是: 1. 在修改请求体后,我们修改了请求,但是请求头中的`Content-Length`没有更新。这是HTTP协议中常见的问题:当我们修改了请求体,请求体的长度发生了变化,但是请求头中的`Content-Length`仍然是原来的值,从而导致服务器解析请求时出错(因为实际内容长度与`Content-Length`不匹配),进而返回500错误。 在`hijackHTTPRequest`函数中,我们使用`poc.ReplaceHTTPPacketBody`替换请求体,这个函数内部应该会自动更新`Content-Length`。但是,在`beforeRequest`函数中,我们使用的是`poc.ReplaceBody`,我们需要确认这个函数是否也会自动更新`Content-Length`。 查看Yakit文档(或函数注释):`poc.ReplaceBody`的第三个参数是`autoContentLength`,这里我们传入的是`false`。这意味着不会自动更新`Content-Length`。因此,修改后的请求体长度变了,但`Content-Length`还是原来的长度,导致服务器解析错误。 解决方案:将`autoContentLength`参数设置为`true`,让函数自动更新`Content-Length`。 修改代码: ```go req = poc.ReplaceBody(req, []byte(encryptedBytes), true) // 将第三个参数改为true ``` 另外,注意在`beforeRequest`函数中,我们获取URL的方式: ```go url = poc.GetUrlFromHTTPRequest("https" /*type: string*/, req /*type: []byte*/) ``` 这里硬编码了`"https"`,但实际上应该根据参数`ishttps`(布尔值)来决定是http还是https。但是,由于我们只关心URL是否以"Example Domain"开头,这个错误可能不会直接影响,但最好修正。 修正URL获取: ```go scheme := "http" if ishttps { scheme = "https" } url = poc.GetUrlFromHTTPRequest(scheme, req) ``` 但根据问题描述,错误是在增加`beforeRequest`后出现的,所以主要问题可能是`Content-Length`未更新。 此外,还有一点:在`beforeRequest`函数的最后,我们返回了`[]byte(req)`。实际上,`req`已经是`[]byte`,所以直接`return req`即可。不过,由于`[]byte(req)`等同于`req`,所以也不是错误。 所以,重点修改`ReplaceBody`的第三个参数为`true`。 修改后的`beforeRequest`函数: ```go beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){ req_method = poc.GetHTTPRequestMethod(req /*type: []byte*/) // 修正URL的scheme scheme := "http" if ishttps { scheme = "https" } url = poc.GetUrlFromHTTPRequest(scheme, req /*type: []byte*/) req_body = poc.GetHTTPPacketBody(req) //req_body返回的是byte类型,codec的sm4加密函数需要传入string,加密完再转为hex println("tt:",json.dumps(req_body)) if req_method == "POST" && url.StartsWith("Example Domain") && req_body != nil { // 加密 encryptedvalue = codec.Sm4ECBEncryptWithPKCSPadding(sm4keyBytes /*type: []byte*/, string(req_body), nil /*type: []byte*/)~ encryptedHex = codec.EncodeToHex(encryptedvalue) encryptedBytes = "request=" + string(encryptedHex) // 修改请求体,并自动更新Content-Length req = poc.ReplaceBody(req /*type: []byte*/, []byte(encryptedBytes) /*type: []byte*/, true /*type: bool*/) return req // 直接返回req,因为已经是[]byte } // 注意:如果没有进入if,应该返回原始请求req(即不修改) return req } ``` 注意:在Yak的MITM热加载中,`beforeRequest`函数需要返回修改后的请求(字节数组)。所以,我们必须在所有分支都返回请求。原代码中,如果没有进入if,则没有返回值(在Go中,没有返回值会导致错误,但在Yak中可能是允许的?)但为了安全,我们应该在函数最后返回原始请求。 因此,我们在最后加上`return req`。 修改后的完整代码: ```go beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){ req_method = poc.GetHTTPRequestMethod(req /*type: []byte*/) // 修正URL的scheme scheme := "http" if ishttps { scheme = "https" } url = poc.GetUrlFromHTTPRequest(scheme, req /*type: []byte*/) req_body = poc.GetHTTPPacketBody(req) //req_body返回的是byte类型,codec的sm4加密函数需要传入string,加密完再转为hex println("tt:",json.dumps(req_body)) if req_method == "POST" && url.StartsWith("Example Domain") && req_body != nil { // 加密 encryptedvalue = codec.Sm4ECBEncryptWithPKCSPadding(sm4keyBytes /*type: []byte*/, string(req_body), nil /*type: []byte*/)~ encryptedHex = codec.EncodeToHex(encryptedvalue) encryptedBytes = "request=" + string(encryptedHex) // 修改请求体,并自动更新Content-Length req = poc.ReplaceBody(req /*type: []byte*/, []byte(encryptedBytes) /*type: []byte*/, true /*type: bool*/) return req } // 如果没有修改,返回原始请求 return req } ``` 这样修改后,应该能解决500错误。 另外,注意加密函数`codec.Sm4ECBEncryptWithPKCSPadding`的第二个参数是字符串,如果请求体是二进制数据(比如已经有加密数据),转换成字符串可能会损坏数据。但是,根据上下文(在`hijackHTTPRequest`中解密时,我们使用同样的方式将字节数组转字符串,然后解密),这里应该没问题。如果请求体是二进制,则不应该转字符串,而是直接加密。但根据问题描述,原始请求体是明文字符串,所以这里使用string转换是安全的。 总结:主要问题是修改请求体后没有更新`Content-Length`头,导致服务器解析错误。将`ReplaceBody`的第三个参数改为`true`即可。 如果仍然有问题,可以检查日志输出,确认加密后的字符串格式是否正确(是否带有非法字符等),以及服务器是否能够正确解密(可以对比客户端原始请求和服务器接收到的请求)。 </think> 您的代码中出现了一个关键问题:在 `beforeRequest` 函数中修改请求后,没有正确处理返回值。Yakit MITM 的 `beforeRequest` 回调需要显式返回修改后的请求字节数组(`[]byte`),但您的代码在 `if` 分支外缺少默认返回值。 ### 错误分析 问题出在以下部分: ```go beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){ // ... if req_method == "POST" && url.StartsWith("Example Domain") && req_body != nil { // ... 修改逻辑 ... return []byte(req) // 正确返回修改后的请求 } // 缺少默认返回值! } ``` ### 修复方案 修改 `beforeRequest` 函数,确保所有执行路径都返回有效的请求字节数组: ```go beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){ req_method = poc.GetHTTPRequestMethod(req) scheme := "http" if ishttps { scheme = "https" } url = poc.GetUrlFromHTTPRequest(scheme, req) req_body = poc.GetHTTPPacketBody(req) if req_method == "POST" && url.StartsWith("Example Domain") && req_body != nil { // 1. 加密请求体 encryptedValue := codec.Sm4ECBEncryptWithPKCSPadding(sm4keyBytes, string(req_body), nil) // 2. 转换为十六进制并添加前缀 encryptedHex := codec.EncodeToHex(encryptedValue) encryptedBytes := "request=" + string(encryptedHex) // 3. 替换请求体并更新Content-Length modifiedReq := poc.ReplaceBody(req, []byte(encryptedBytes), true) // 4. 返回修改后的请求 return modifiedReq } // 5. 未修改时返回原始请求 return req } ``` ### 关键修复点说明 1. **添加默认返回值**: ```go return req // 确保所有分支都有返回值 ``` 2. **自动更新Content-Length**: ```go poc.ReplaceBody(req, []byte(encryptedBytes), true) // 第三个参数true表示自动更新Content-Length ``` 这是关键修复点 - 当请求体长度变化时,必须更新Content-Length头,否则服务器会因长度不匹配返回500错误 3. **变量作用域优化**: 使用局部变量 `encryptedValue`, `encryptedHex` 避免全局污染 4. **URL scheme处理**: ```go scheme := "http" if ishttps { scheme = "https" } ``` 确保正确处理HTTPS请求的URL ### 完整程调试建议 1. **添加调试日志**: ```go println("原始请求体长度:", len(req_body)) println("加密后长度:", len(encryptedBytes)) println("修改后请求:", string(modifiedReq)) ``` 2. **验证加密解密程**: ```go // 在beforeRequest中加密后立即解密验证 decrypted := codec.Sm4ECBDecryptWithPKCSPadding(sm4keyBytes, encryptedValue, nil) println("加密解密验证:", string(decrypted) == string(req_body)) ``` 3. **检查Content-Length**: ```go contentLen := poc.GetHTTPPacketHeaders(req)["Content-Length"] println("原始长度:", contentLen) contentLenModified := poc.GetHTTPPacketHeaders(modifiedReq)["Content-Length"] println("修改后长度:", contentLenModified) ``` ### 注意事项 1. **字符串转换风险**: ```go string(req_body) // 可能丢失二进制数据 ``` 如果请求体包含非UTF8数据,应直接处理字节数组: ```go // 使用字节数组版本(如果库支持) encryptedValue := codec.Sm4ECBEncryptBytesWithPKCSPadding(sm4keyBytes, req_body, nil) ``` 2. **URL匹配优化**: ```go url.StartsWith("Example Domain") // 可能匹配失败 ``` 建议使用更可靠的匹配方式: ```go strings.Contains(url, "example.com/api") // 或 regexp.Match(`https?://example\.com/api/.+`) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值