Delphi、Indy TLS 1.2 和 TLS 1.3 开始状态码 403

Posted

技术标签:

【中文标题】Delphi、Indy TLS 1.2 和 TLS 1.3 开始状态码 403【英文标题】:Delphi, Indy TLS 1.2 and TLS 1.3 gettin status code 403 【发布时间】:2022-01-01 00:35:51 【问题描述】:

我有一个问题,我不知道如何解决。 几天以来,我无法通过 indy 登录到服务器(https://auth.bmwgroup.com、https://aos.bmwgroup.com、https://carver.bmwgroup.com)。

在此服务器上启用了 TLS 1.2 和 TLS 1.3。

所以我启动 HttpAnalyzer 来检查问题出在哪里并且我的程序开始工作,然后我关闭 HttpAnalyzer 并且我的程序不再工作了。

谁能帮忙解决这个问题。

我正在使用此代码:

procedure TForm1.Server_NewInit;
var
    List: TStringList;
    URL, AuthID, ID_USER, ID_URL : AnsiString;
    JSON: TStringStream;
    l,p : string;
begin
    idHttpC := TIdHTTP.Create(nil);
    idHttpC.ConnectTimeout := 80000;
    idHttpC.ReadTimeout := 80000;
    idHttpC.AllowCookies := true;

// config Redirect's
    idHttpC.RedirectMaximum := 35;
    idHttpC.HandleRedirects := true;
    idHttpC.HTTPOptions := [hoKeepOrigProtocol, hoTreat302Like303];
    idHttpC.OnRedirect := IdHTTP1Redirect;

// create Cookie's
    idCookieC := TIdCookieManager.Create(IdHttpC);
    idHttpC.CookieManager := idCookieC;

// create OpenSSL
    lIOHandlerC := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
    lIOHandlerC.SSLOptions.Mode := sslmClient;
    lIOHandlerC.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
    idHttpC.IOHandler := lIOHandlerC;

// Nr1 execute redirect
..
// Nr2 serverinfo
..
// Nr3 getSessionInfo
..
// Nr4 internetb2x empty post to get AuthId
..
// Nr5 internetb2x POST JSON - authorisation nr1
..
// Nr6 getSessionInfo after login
..
// check for authorisation give me ok, so, pass and login are ok
..
// Nr7 step idFromSession
..
// Nr8 step get info about user id
..
// Nr9 execute new ID_URL
..
// Nr10 authorisation Nr 2
idHttpC.Request.Referer := idHttpC.URL.GetFullURI([]);
idHttpC.Request.CacheControl := 'no-cache';
idHttpC.Request.ContentType := 'application/x-www-form-urlencoded';
...
here i am getting problem

从 1 到 9 的步骤没有任何问题,我比较了标题和内容数据,它们与启动的分析器相同。

但是第 nr10 步给了我启动分析器的状态

**RT HTTP/1.1 200 OK
RC 200
CT text/html**

Date: Mon, 22 Nov 2021 21:09:10 GMT
Server: Apache
Last-Modified: Thu, 27 May 2021 09:09:24 GMT
ETag: "1b8-5c34c1d88f900"
Accept-Ranges: bytes
Strict-Transport-Security: max-age=8640000; includeSubDomains
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-WebKit-CSP: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 1; mode=block
X-Frame-Options: sameorigin
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
content-type: text/html; charset=iso-8859-1
Content-Length: 440
refresh: 0; URL=/carver_www/carverMain.jsp

并且没有分析器

**RT HTTP/1.1 403 Forbidden
RC 403
CT text/html** 

Date: Mon, 22 Nov 2021 21:10:20 GMT
Server: Apache
Vary: accept-language,accept-charset
Accept-Ranges: bytes
Strict-Transport-Security: max-age=8640000; includeSubDomains
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-WebKit-CSP: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 1; mode=block
X-Frame-Options: sameorigin
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Content-Language: de

第 2 部分:

在这里请求nr9

// prepare new Request for ID_URL
    idHttpC.Request.Clear;
    idHttpC.Request.CustomHeaders.Clear;
    idHttpC.Request.Accept := 'text/html, application/xhtml+xml, */*';
    idHttpC.Request.AcceptLanguage := 'de-DE';
    idHttpC.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';
    idHttpC.Request.Host := 'auth.bmwgroup.com';
    idHttpC.Request.CustomHeaders.Values['Connection'] := 'Keep-Alive';
    idHttpC.Request.CustomHeaders.Values['DNT'] := '1';
    idHttpC.Request.AcceptEncoding := 'gzip, deflate';

// Nr9 execute new ID_URL
    try
        res1 := idHttpC.Get(ID_URL);
        HTML_SaveToFile(res1, 'sFile9.txt');
        idHttpC.Response.RawHeaders.SaveToFile('RH9.txt');
    except
        on e:EIdSocketError do
            ShowMessage('EIdSocketError: ' + e.Message);
        on e:EIdReadTimeout do
            ShowMessage('EIdReadTimeout: ' + e.Message);
        on e:EIDHttpProtocolException do
            ShowMessage('EIDHttpProtocolException: ' + IntToStr(e.ErrorCode));
        on e:Exception do
            ShowMessage('Exception: ' + e.Message);
    end;
    Memo4.Lines.Add('Nr9 done');

通过这个请求,我得到了文件:

<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="description" content="OAuth 2.0 Form Post">
        <title>Submit This Form</title>
    </head>
    <body onload="javascript:document.forms[0].submit()">
        <form method="post" action="https://carver.bmwgroup.com:443/agent/cdsso-oauth2">
            <input type="hidden" name="id_token" value="...."/><input type="hidden" name="state" value="...."/>
        </form>
    </body>
</html>

我在第 10 步中执行的这段代码:

// Nr10 authorisation Nr 2
    idHttpC.Request.Clear;
    idHttpC.Request.Accept := 'text/html, application/xhtml+xml, */*';
    idHttpC.Request.Referer := idHttpC.URL.GetFullURI([]);
    idHttpC.Request.AcceptLanguage := 'de-DE';
    idHttpC.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';
    idHttpC.Request.ContentType := 'application/x-www-form-urlencoded';
    idHttpC.Request.Host := 'carver.bmwgroup.com';
    idHttpC.Request.CustomHeaders.Values['Connection'] := 'Keep-Alive';
    idHttpC.Request.CustomHeaders.Values['DNT'] := '1';
    idHttpC.Request.CacheControl := 'no-cache';
    idHttpC.Request.AcceptEncoding := 'gzip, deflate';

    List := TStringList.Create;
    List.Add('id_token='+JSON_GET_ID(res1, 3, 0));
    List.Add('state='+JSON_GET_ID(res1, 4, 0));
    try
        res1 := idHttpC.Post('https://carver.bmwgroup.com/agent/cdsso-oauth2', List);
        FReeAndnil(List);
        Memo4.Lines.Add('RT' + idHttpC.Response.ResponseText);
        Memo4.Lines.Add('RC' + IntToStr(idHttpC.Response.ResponseCode));
        Memo4.Lines.Add('CT' + idHttpC.Response.ContentType);
        HTML_SaveToFile(res1, 'sFile10.txt');
        idHttpC.Response.RawHeaders.SaveToFile('RH10.txt');
    except
        on e:EIdSocketError do
            ShowMessage('EIdSocketError: ' + e.Message);
        on e:EIdReadTimeout do
            ShowMessage('EIdReadTimeout: ' + e.Message);
        on e:EIDHttpProtocolException do
            ShowMessage('EIDHttpProtocolException: ' + IntToStr(e.ErrorCode));
        on e:Exception do
            ShowMessage('Exception: ' + e.Message);
    end;
    Memo4.Lines.Add('Nr10 done');

【问题讨论】:

如果没有看到第 10 步的实际 HTTP 请求,真的很难诊断这个问题。但是,我看不出代码有什么问题。 FORBIDDEN 回复中有一个非空正文,它是否说明了请求被拒绝的原因? @RemyLebeau 我添加了一些代码。我启动了 Wireshark 并且代码不起作用,然后我在暂停时启动 HttpAnalyzer,代码工作正常,我停止登录 HttpAnalyzer,代码不起作用。 你没有回答我的问题。 403 响应的正文内容实际上是在说什么?您还没有显示任何原始 HTTP 请求标头/数据。例如,cookie 是否正确发送回服务器,尤其是在第 10 步?无论如何,我没有看到您的代码有任何真正的问题(尽管我确实看到了一些错误,但它们与您的问题无关),因此问题必须在其他地方。顺便说一句,你为什么使用 JSON 解析器从 HTML 中提取值? 您显示的代码中没有任何内容表明正在使用Compressor,但是分配Request.AcceptEncoding 几乎总是一个错误,除非您准备手动解压缩/解码响应正文。否则,根本不要分配AcceptEncoding,让TIdHTTP 在内部管理它。我看到的其他错误:1)分配Request.Host,让TIdHTTP 管理它。 2)分配给Request.CustomHeaders.Values['Connection'],应该分配给Request.Connection。 3) 在Request.Clear() 之后调用Request.CustomHeaders.Clear() 是多余的。 "在我执行 Request.Clear() 后仍有一些 CustomHeaders" - 有趣,我没想到会这样。这可能是一个需要修复的错误。 【参考方案1】:

授权

出于好奇,我用谷歌搜索了 bmwgroup api。通过快速扫描,我看到 API 需要传递不记名令牌。我没看到你通过那个。

通常您首先通过第一次调用获得令牌。在您的描述中,您写道该部分有效,所以我猜您在上一步中收到了您的令牌。

这是一个可能看起来像这样的标题: Authorization: Bearer xxx,其中 xxx 是您的令牌。

或者至少你没有在你拥有的地方粘贴任何代码。

更快的调试

无论如何,在遇到此类问题时调试 Delphi 代码可能既困难又缓慢。

我通常做的是抓取 Postman 之类的东西或使用 JetBrains IDE 并创建一个 .http 文件(我也看到人们使用 VSCode 中的 .http 文件)。

我调整我的请求,直到我可以“手动”使其工作。然后我向后工作以创建具有相同功能的 Delphi 代码。 这种方法通常比更新 pascal 代码、编译、运行、等待断点命中、检查调试器中发生的情况等要快得多。

【讨论】:

问题出在第 9 步,我得到了Accept-Ranges: bytes Content-Encoding: gzip。在我的程序中,我没有将 Compressor 分配给 indy,因此得到了压缩数据,无法提取令牌和状态以在步骤 10 中发送它。 @EvgenyLevitskiy:那么它现在工作了吗? 是的,现在它的工作。 idHttpC.Compressor := TIdCompressorZLib.Create(idHttpC);【参考方案2】:

问题出在第 nr9 步,我在其中获得了 indy 压缩数据:

Accept-Ranges: bytes
Content-Encoding: gzip

为什么代码使用 httpAnalyzer ?因为 httpAnalyzer 通过自有库记录网络协议,在这种情况下 httpAnalyzer 为我解压缩数据。

添加后

idHttpC.Compressor := TIdCompressorZLib.Create(idHttpC);

代码再次运行。

【讨论】:

真正的问题是您手动设置idHttpC.Request.AcceptEncoding := 'gzip, deflate'; 开头。通过设置,您告诉服务器您接受压缩数据,即使您真的不接受。通过分配TIdHTTP.Compressor,它可以自动解压数据。无论哪种方式,您都不应该手动设置Request.AcceptEncoding,让TIdHTTP 根据其可用功能在内部为您设置它。如果您没有手动分配Request.AcceptEncoding,也没有分配CompressorTIdHTTP 不会向服务器请求压缩数据... ... 但是即使分配了Compressor,仍然不能保证压缩数据实际上可以自动解压缩(检查TIdCompressorZLib.IsReady 属性),在这种情况下您仍然 不应手动设置Request.AcceptEncoding。如果Compressor 准备就绪,TIdHTTP 将在内部更新Request.AcceptEncoding 以启用gzip, deflate 来请求压缩。但是如果你把Request.AcceptEncoding留空,而Compressor还没有准备好,TIdHTTP就不会更新Request.AcceptEncoding,所以就不会要求压缩数据了。

以上是关于Delphi、Indy TLS 1.2 和 TLS 1.3 开始状态码 403的主要内容,如果未能解决你的问题,请参考以下文章

THTTPRio TLS 1.2

私钥密码中包含非 ASCII 字符的 Indy TLS 服务器

在高级设置中启用 TLS 1.0、TLS 1.1 和 TLS 1.2,

如何强制 java 服务器只接受 tls 1.2 并拒绝 tls 1.0 和 tls 1.1 连接

Powershell CREDSSP 和 TLS 1.2

强制 HttpWebRequest 和 ClientWebSocket 使用 TLS 1.2