mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-03 10:18:42 +00:00
XHTTP transport: Bugfixes for obfuscations (#5720)
https://github.com/XTLS/Xray-core/pull/5720#issuecomment-4016290343
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package splithttp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -54,11 +57,72 @@ func (c *Config) GetRequestHeader() http.Header {
|
||||
return header
|
||||
}
|
||||
|
||||
func (c *Config) WriteResponseHeader(writer http.ResponseWriter) {
|
||||
|
||||
func (c *Config) GetRequestHeaderWithPayload(payload []byte) http.Header {
|
||||
header := c.GetRequestHeader()
|
||||
|
||||
key := c.UplinkDataKey
|
||||
encodedData := base64.RawURLEncoding.EncodeToString(payload)
|
||||
|
||||
for i := 0; len(encodedData) > 0; i++ {
|
||||
chunkSize := min(int(c.GetNormalizedUplinkChunkSize().rand()), len(encodedData))
|
||||
chunk := encodedData[:chunkSize]
|
||||
encodedData = encodedData[chunkSize:]
|
||||
headerKey := fmt.Sprintf("%s-%d", key, i)
|
||||
header.Set(headerKey, chunk)
|
||||
}
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
func (c *Config) GetRequestCookiesWithPayload(payload []byte) []*http.Cookie {
|
||||
cookies := []*http.Cookie{}
|
||||
|
||||
key := c.UplinkDataKey
|
||||
encodedData := base64.RawURLEncoding.EncodeToString(payload)
|
||||
|
||||
for i := 0; len(encodedData) > 0; i++ {
|
||||
chunkSize := min(int(c.GetNormalizedUplinkChunkSize().rand()), len(encodedData))
|
||||
chunk := encodedData[:chunkSize]
|
||||
encodedData = encodedData[chunkSize:]
|
||||
cookieName := fmt.Sprintf("%s_%d", key, i)
|
||||
cookies = append(cookies, &http.Cookie{Name: cookieName, Value: chunk})
|
||||
}
|
||||
|
||||
return cookies
|
||||
}
|
||||
|
||||
func (c *Config) WriteResponseHeader(writer http.ResponseWriter, requestMethod string, requestHeader http.Header) {
|
||||
// CORS headers for the browser dialer
|
||||
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
writer.Header().Set("Access-Control-Allow-Methods", "*")
|
||||
// writer.Header().Set("X-Version", core.Version())
|
||||
if origin := requestHeader.Get("Origin"); origin == "" {
|
||||
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
} else {
|
||||
// Chrome says: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
|
||||
writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
|
||||
if c.GetNormalizedSessionPlacement() == PlacementCookie ||
|
||||
c.GetNormalizedSeqPlacement() == PlacementCookie ||
|
||||
c.XPaddingPlacement == PlacementCookie ||
|
||||
c.GetNormalizedUplinkDataPlacement() == PlacementCookie {
|
||||
writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
if requestMethod == "OPTIONS" {
|
||||
requestedMethod := requestHeader.Get("Access-Control-Request-Method")
|
||||
if requestedMethod != "" {
|
||||
writer.Header().Set("Access-Control-Allow-Methods", requestedMethod)
|
||||
} else {
|
||||
writer.Header().Set("Access-Control-Allow-Methods", "*")
|
||||
}
|
||||
|
||||
requestedHeaders := requestHeader.Get("Access-Control-Request-Headers")
|
||||
if requestedHeaders == "" {
|
||||
writer.Header().Set("Access-Control-Allow-Headers", "*")
|
||||
} else {
|
||||
writer.Header().Set("Access-Control-Allow-Headers", requestedHeaders)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedUplinkHTTPMethod() string {
|
||||
@@ -110,6 +174,40 @@ func (c *Config) GetNormalizedScStreamUpServerSecs() RangeConfig {
|
||||
return *c.ScStreamUpServerSecs
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedUplinkChunkSize() RangeConfig {
|
||||
if c.UplinkChunkSize == nil || c.UplinkChunkSize.To == 0 {
|
||||
switch c.UplinkDataPlacement {
|
||||
case PlacementCookie:
|
||||
return RangeConfig{
|
||||
From: 2 * 1024, // 2 KiB
|
||||
To: 3 * 1024, // 3 KiB
|
||||
}
|
||||
case PlacementHeader:
|
||||
return RangeConfig{
|
||||
From: 3 * 1000, // 3 KB
|
||||
To: 4 * 1000, // 4 KB
|
||||
}
|
||||
default:
|
||||
return c.GetNormalizedScMaxEachPostBytes()
|
||||
}
|
||||
} else if c.UplinkChunkSize.From < 64 {
|
||||
return RangeConfig{
|
||||
From: 64,
|
||||
To: max(64, c.UplinkChunkSize.To),
|
||||
}
|
||||
}
|
||||
|
||||
return *c.UplinkChunkSize
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
|
||||
if c.ServerMaxHeaderBytes <= 0 {
|
||||
return 8192
|
||||
} else {
|
||||
return int(c.ServerMaxHeaderBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedSessionPlacement() string {
|
||||
if c.SessionPlacement == "" {
|
||||
return PlacementPath
|
||||
@@ -196,24 +294,107 @@ func (c *Config) ApplyMetaToRequest(req *http.Request, sessionId string, seqStr
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) FillStreamRequest(request *http.Request, sessionId string, seqStr string) {
|
||||
request.Header = c.GetRequestHeader()
|
||||
length := int(c.GetNormalizedXPaddingBytes().rand())
|
||||
config := XPaddingConfig{Length: length}
|
||||
|
||||
if c.XPaddingObfsMode {
|
||||
config.Placement = XPaddingPlacement{
|
||||
Placement: c.XPaddingPlacement,
|
||||
Key: c.XPaddingKey,
|
||||
Header: c.XPaddingHeader,
|
||||
RawURL: request.URL.String(),
|
||||
}
|
||||
config.Method = PaddingMethod(c.XPaddingMethod)
|
||||
} else {
|
||||
config.Placement = XPaddingPlacement{
|
||||
Placement: PlacementQueryInHeader,
|
||||
Key: "x_padding",
|
||||
Header: "Referer",
|
||||
RawURL: request.URL.String(),
|
||||
}
|
||||
}
|
||||
|
||||
c.ApplyXPaddingToRequest(request, config)
|
||||
c.ApplyMetaToRequest(request, sessionId, "")
|
||||
|
||||
if request.Body != nil && !c.NoGRPCHeader { // stream-up/one
|
||||
request.Header.Set("Content-Type", "application/grpc")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) FillPacketRequest(request *http.Request, sessionId string, seqStr string) error {
|
||||
dataPlacement := c.GetNormalizedUplinkDataPlacement()
|
||||
|
||||
if dataPlacement == PlacementBody || dataPlacement == PlacementAuto {
|
||||
request.Header = c.GetRequestHeader()
|
||||
} else {
|
||||
var data []byte
|
||||
var err error
|
||||
if request.Body != nil {
|
||||
data, err = io.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
request.Body = nil
|
||||
request.ContentLength = 0
|
||||
switch dataPlacement {
|
||||
case PlacementHeader:
|
||||
request.Header = c.GetRequestHeaderWithPayload(data)
|
||||
case PlacementCookie:
|
||||
request.Header = c.GetRequestHeader()
|
||||
for _, cookie := range c.GetRequestCookiesWithPayload(data) {
|
||||
request.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
length := int(c.GetNormalizedXPaddingBytes().rand())
|
||||
config := XPaddingConfig{Length: length}
|
||||
|
||||
if c.XPaddingObfsMode {
|
||||
config.Placement = XPaddingPlacement{
|
||||
Placement: c.XPaddingPlacement,
|
||||
Key: c.XPaddingKey,
|
||||
Header: c.XPaddingHeader,
|
||||
RawURL: request.URL.String(),
|
||||
}
|
||||
config.Method = PaddingMethod(c.XPaddingMethod)
|
||||
} else {
|
||||
config.Placement = XPaddingPlacement{
|
||||
Placement: PlacementQueryInHeader,
|
||||
Key: "x_padding",
|
||||
Header: "Referer",
|
||||
RawURL: request.URL.String(),
|
||||
}
|
||||
}
|
||||
|
||||
c.ApplyXPaddingToRequest(request, config)
|
||||
c.ApplyMetaToRequest(request, sessionId, seqStr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) ExtractMetaFromRequest(req *http.Request, path string) (sessionId string, seqStr string) {
|
||||
sessionPlacement := c.GetNormalizedSessionPlacement()
|
||||
seqPlacement := c.GetNormalizedSeqPlacement()
|
||||
sessionKey := c.GetNormalizedSessionKey()
|
||||
seqKey := c.GetNormalizedSeqKey()
|
||||
|
||||
if sessionPlacement == PlacementPath && seqPlacement == PlacementPath {
|
||||
subpath := strings.Split(req.URL.Path[len(path):], "/")
|
||||
if len(subpath) > 0 {
|
||||
sessionId = subpath[0]
|
||||
}
|
||||
if len(subpath) > 1 {
|
||||
seqStr = subpath[1]
|
||||
}
|
||||
return sessionId, seqStr
|
||||
var subpath []string
|
||||
pathPart := 0
|
||||
if sessionPlacement == PlacementPath || seqPlacement == PlacementPath {
|
||||
subpath = strings.Split(req.URL.Path[len(path):], "/")
|
||||
}
|
||||
|
||||
switch sessionPlacement {
|
||||
case PlacementPath:
|
||||
if len(subpath) > pathPart {
|
||||
sessionId = subpath[pathPart]
|
||||
pathPart += 1
|
||||
}
|
||||
case PlacementQuery:
|
||||
sessionId = req.URL.Query().Get(sessionKey)
|
||||
case PlacementHeader:
|
||||
@@ -225,6 +406,11 @@ func (c *Config) ExtractMetaFromRequest(req *http.Request, path string) (session
|
||||
}
|
||||
|
||||
switch seqPlacement {
|
||||
case PlacementPath:
|
||||
if len(subpath) > pathPart {
|
||||
seqStr = subpath[pathPart]
|
||||
pathPart += 1
|
||||
}
|
||||
case PlacementQuery:
|
||||
seqStr = req.URL.Query().Get(seqKey)
|
||||
case PlacementHeader:
|
||||
|
||||
Reference in New Issue
Block a user