代码之家  ›  专栏  ›  技术社区  ›  ntg

Keycloack:用户可以通过登录到另一个域来访问一个域

  •  0
  • ntg  · 技术社区  · 5 年前

    我有一个nginx/openresty客户端到keycloack服务器,使用openid进行授权。 我正在使用 lua-resty-openidc 以允许访问代理后面的服务。

    我在两个不同的领域为不同的服务创建了两个客户端。

    问题是,在用户在第一个领域通过身份验证后,例如。 https://<my-server>/auth/realms/<realm1>/protocol/openid-connect/auth?response_type=code&client_id=openresty&state=........... ,他可以直接访问其他服务 realm2 也。

    这里发生了什么事?我如何确保用户只能访问其身份验证所针对的领域的客户端?

    我如何确保注销后,用户在重新登录之前将无法再访问?

    [编辑详细信息] 这两个服务的nginx.conf如下。 用户首先访问 https://<my-server>/service_1/ 并被重定向到keycloack以提供realm1的密码。他提供了它,并且能够访问service_1。

    然而,如果在那之后他试图进入 https://<my-server>/service_2/ ,他不再需要进行身份验证,但可以登录,尽管service2是关于不同领域的客户端,具有不同的client_secret!

    ..... 位置/服务_1/{

        access_by_lua_block {
            local opts = {
                redirect_uri_path = "/service_1/auth", -- we are send here after auth
                discovery = "https://<my-server>/keycloak/auth/realms/realm1/.well-known/openid-configuration",
                client_id = "openresty",
                client_secret = "<client1-secret>",
                session_contents = {id_token=true} -- this is essential for safari!
            }
            -- call introspect for OAuth 2.0 Bearer Access Token validation
            local res, err = require("resty.openidc").authenticate(opts)
    
            if err then
                ngx.status = 403
                ngx.say(err)
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
        }
    
        # I disabled caching so the browser won't cache the site.
        expires           0;
        add_header        Cache-Control private;
    
        proxy_pass http://<server-for-service1>:port1/foo/;
        proxy_set_header Host $http_host;
    
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_buffering off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    
    }
    

    ..........................

    location /service_2/ {
    
        access_by_lua_block {
            local opts = {
                redirect_uri_path = "/service_2/auth", -- we are send here after auth
                discovery = "https://<my-server>/keycloak/auth/realms/realm2/.well-known/openid-configuration",
                client_id = "openresty",
                client_secret = "client2-secret",
                session_contents = {id_token=true} -- this is essential for safari!
            }
            -- call introspect for OAuth 2.0 Bearer Access Token validation
            local res, err = require("resty.openidc").authenticate(opts)
    
            if err then
                ngx.status = 403
                ngx.say(err)
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
        }
    
        # I disabled caching so the browser won't cache the site.
        expires           0;
        add_header        Cache-Control private;
    
        proxy_pass http://<server-for-service2>:port2/bar/;
        proxy_set_header Host $http_host;
    
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_buffering off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    
    }
    

    [编辑详细信息2]

    我使用的是lua-resty openidc 1.7.2版本,但根据两个版本代码的差异,我写的所有内容都应该代表1.7.4。

    我可以从调试级别日志中清楚地看到,会话是在第一次访问期间创建的,然后在第二个领域上重用,这是错误的,因为第二次访问仍然有第一个领域的令牌。。。以下是授权内容 realm2 看起来像…:

    2021/04/28 12:56:41 [debug] 2615#2615: *4617979 [lua] openidc.lua:1414: authenticate(): session.present=true, session.data.id_token=true, session.data.authenticated=true, opts.force_reauthorize=nil, opts.renew_access_token_on_expiry=nil, try_to_renew=true, token_expired=false
    2021/04/28 12:56:41 [debug] 2615#2615: *4617979 [lua] openidc.lua:1470: authenticate(): id_token={"azp":"realm1","typ":"ID","iat":1619614598,"iss":"https:\/\/<myserver>\/keycloak\/auth\/realms\/realm1","aud":"realm1","nonce":"8c8ca2c4df2...b26"
    ,"jti":"1c028c65-...0994f","session_state":"0e1241e3-66fd-4ca1-a0dd-c0d1a6a5c708","email_verified":false,"sub":"25303e44-...e2c1757ae857","acr":"1","preferred_username":"logoutuser","auth_time":1619614598,"exp":1619614898,"at_hash":"5BNT...j414r72LU6g"}
    
    0 回复  |  直到 5 年前
        1
  •  0
  •   ntg    5 年前

    好吧,这花了我一些时间。也可能是大多数教程都忽略了这一点 一个域具有身份验证访问权限的漏洞(仅在单个nginx使用多个域的设置中)将允许对任何其他域的身份验证访问。

    来自教程的一个典型的自我证明调用是:

    location /service1/ {
    
        access_by_lua_block {
            local opts = {
                redirect_uri_path = "/realm1/authenticated",
                discovery = "https://<myserver>/keycloak/auth/realms/realm1/.well-known/openid-configuration",
                client_id = "client1",
                client_secret = <........>,
                session_contents = {id_token=true} -- this is essential for safari!
            }
            -- call introspect for OAuth 2.0 Bearer Access Token validation
            local res, err = require("resty.openidc").authenticate(opts)
    
            if err then
                ngx.status = 403
                ngx.say(err)
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
        }
    
        # I disbled caching so the browser won't cache the site.
        expires           0;
        add_header        Cache-Control private;
    
        proxy_pass http://realm1-server:port/service1/;
        proxy_set_header Host $http_host;
    
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_buffering off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    
    }
    location /service2/ {
     <same for ream2> 
    }
    

    实际上似乎有两个问题

    1. 我们不检查领域id(这是一个漏洞)
    2. 两个领域的会话可以互换缓存(这将导致这样一种情况,即如果我们修复(1),我们现在只允许访问一个领域,并且必须从realm1注销才能访问realm2)

    解决: 1) 我们需要明确检查领域是否正确 2) 我们应该为每个领域使用一个会话表(请注意,虽然这似乎也能解决(1)问题,但如果攻击者将会话id与他的“特殊”浏览器混合使用,它就不会解决这个问题——至少我认为是这样)

    对于no2,没有文档,我必须阅读代码 openidc.lua 从那里,这个库使用的库的代码( session.lua )

    变化如下: 位置/服务1/{

        access_by_lua_block {
            local opts = {
                redirect_uri_path = "/realm1/authenticated",
                discovery = "https://<myserver>/keycloak/auth/realms/realm1/.well-known/openid-configuration",
                client_id = "client1",
                client_secret = <........>,
                session_contents = {id_token=true} -- this is essential for safari!
            }
            -- call introspect for OAuth 2.0 Bearer Access Token validation
            local res, err = require("resty.openidc").authenticate(opts,nil,nil,{name=opts.client_id})
    
            if (err or ( res.id_token.azp ~= opts.client_id ) ) then
                ngx.status = 403
                ngx.say(err)
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
        }
        <..................no changes here................>
    }
    location /service2/ {
     <same for ream2> 
    }