Package: osbtlib
osbtlibScenario description library foroidc-scenario-based-tester/osbtInstallhttps://pypi.org/project/osbtlib/pip install osbtlibSetupInstallation ofPython,pipandoidc-scenario-based-tester/osbtis required.Quick StartWrite a test scenario script.$ touch scenario.pyimportsysimportosimporttimeimportjwtfromosbtlibimportBrowserSimulator,Osbtlib# Test Informationtest_name="IDSpoofing"test_description="- The attacker op modifies the id_token to impersonate the victim <br> - The sub claim of the id_token is modified to the victim's sub claim"outcome="failed"err_msg=""countermeasure="- Check the signature of the id_token <br> - Check the iss claim of the id_token <br> - Check the sub claim of the id_token"ATTACKER_OP_ENDPOINT="http://localhost:9997"HONEST_RP_ENDPOINT="http://localhost:9999"PROXY_SERVER_ENDPOINT="http://localhost:8080"osbt=Osbtlib(attacker_op_url=ATTACKER_OP_ENDPOINT)bs=BrowserSimulator(f'{HONEST_RP_ENDPOINT}/login?issuer={ATTACKER_OP_ENDPOINT}/',PROXY_SERVER_ENDPOINT)try:# create malicious id_tokenmalicious_id_token=jwt.encode({"issuer":"http://localhost:9997/","sub":"hoge"},key="",algorithm="none")# send order to attacker opres=osbt.attacker_op.replace_id_token(malicious_id_token)print("request sent:",res)time.sleep(5)# victim credentialsvictim_username='test-user@localhost'victim_password='verysecure'# browser simulationsso_flow=f"""page.locator('input[name="username"]').fill('{victim_username}')page.locator('input[name="password"]').fill('{victim_password}')page.locator('button[type="submit"]').click()print(page.content())"""bs.run(sso_flow)content=bs.get_content()print("content:",content)bs.close()# result checkif"issuer does not match"incontent:outcome="pass"osbt.attacker_op.clean()osbt.cli.send_result(test_name,test_description,outcome,err_msg,countermeasure)exceptExceptionase:print('Error:',e)osbt.attacker_op.clean()outcome="failed"err_msg=str(e)osbt.cli.send_result(test_name,test_description,outcome,err_msg,countermeasure)Run example RP and attacker OP.# start oidc op server
$ attacker-op

# start oidc web client (in a new terminal)
$ git clone https://github.com/oidc-scenario-based-tester/osbt.git
$ cd osbt
$ CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9997/ SCOPES="openid profile" PORT=9999 go run github.com/oidc-scenario-based-tester/osbt/oidc/rp/user-selectedRun osbt server and proxy server extension.$ osbt server
$ mitmdump --ssl-insecure -s proxy-extension.pyThen execute scenario script byosbt run.$ osbt run -f scenario.py -t 30sUsageImportimportOsbtlib,BrowserSimulatorfromosbtlibOsbtlibincludes a client that interacts with proxy extensions, a client that interacts with the Attacker OP, and the ability to manipulate ID Token.BrowserSimulatorincludes the ability to automate browser operations usingPlayWright.osbt=Osbtlib(proxy_extension_url="http://localhost:5555",attacker_op_url="http://localhost:9997",cli_server_url="http://localhost:54454")bs=BrowserSimulator(url="http://localhost:9999/login",proxy_url="http://localhost:8080")Create an instance ofOsbtlib. The arguments are as follows:proxy_extension_url: URL of proxy extension serverattacker_op_url: URL of attacker opcli_server_url: URL of osbt serverCreate an instance ofBrowserSimulator. The arguments are as follows:url: URL of login pageproxy_url: URL of proxy serverProxy Server OperationOsbtlib.proxyadd, modify request headerosbt.proxy.add_header("header_name","header_value","host","path","method")osbt.proxy.modify_header("modified_header_name","header_value","host","path","method")add_header(name: str, value: str, host: str = None, path: str = None, method: str = None) -> dictname: Name of the header to be addedvalue: Value of the header to be addeddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the header will be added if the conditions are met.modify_header(name: str, value: str, host: str = None, path: str = None, method: str = None) -> dictname: Name of the header to be replacedvalue: Value of the header to be replaceddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the header will be modified if the conditions are met.add, modify request query paramosbt.proxy.add_query_param("param_name","param_value","host","path","method")osbt.proxy.modify_query_param("modified_param_name","param_value","host","path","method")add_query_param(name: str, value: str, host: str = None, path: str = None, method: str = None) -> dictname: Name of the query param to be addedvalue: Value of the query param to be addeddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the query param will be added if the conditions are met.modify_query_param(name: str, value: str, host: str = None, path: str = None, method: str = None) -> dictname: Name of the query param to be replacedvalue: Value of the query param to be replaceddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the query param will be modified if the conditions are met.add, modify request body paramosbt.proxy.add_body_param("param_name","param_value","host","path","method")osbt.proxy.modify_body_param("modified_param_name","param_value","host","path","method")add_body_param(name: str, value: str, host: str = None, path: str = None, method: str = None) -> dictname: Name of the body param to be addedvalue: Value of the body param to be addeddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the body param will be added if the conditions are met.modify_body_param(name: str, value: str, host: str = None, path: str = None, method: str = None) -> dictname: Name of the body param to be replacedvalue: Value of the body param to be replaceddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the body param will be modified if the conditions are met.intercept request/responseosbt.proxy.intercept_request("condition")osbt.proxy.intercept_response("condition")intercept_request(host: str = None, path: str = None, method: str = None) -> dictcondition: Requests containing this string are droppeddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the request will be intercepted if the conditions are met.intercept_response(host: str = None, path: str = None, method: str = None) -> dictcondition: Responses containing this string are droppeddomain: Condition1, host name of the requestpath: Condition2, path of the requestmethod: Condition3, method of the requestIf you specify conditions, the response will be intercepted if the conditions are met.get request/response historyosbt.proxy.get_history()get_history() -> dictdelete all rules and historyosbt.proxy.clean()clean() -> dictAttacker OP OperationOsbtlib.attacker-opID Token Replacement for Responsesosbt.attacker_op.replace_id_token("[id_token]")replace_id_token(id_token: str) -> boolid_token: The value after replacement of the ID token in the response from the Attacker OP.Providing malicious endpoints using the Discovery serviceosbt.attacker_op.set_malicious_endpoints({"authorization_endpoint":"http://localhost:9999/auth"})set_malicious_endpoints(endpoints: dict) -> boolendpoints: Change the endpoints that Attacker OP returns when/.well-known/openid-configurationis accessed. The following endpoints can be changed.authorization_endpointtoken_endpointuserinfo_endpointregistration_endpointRedirect to Honest OP upon an authentication requestosbt.attacker_op.idp_confusion("http://localhost:9998/auth")idp_confusion(honest_op_auth_endpoint: str) -> boolhonest_op_auth_endpoint:authorization_endpointof the honest OP. When an authentication request is sent to the Attacker OP, it is redirected to this endpoint with the query parameters passed on.Attacker Server OperationOsbtlib.requestbinGet Requestbin historyosbt.requestbin.get_requestbin_history("PIPEDREAM_TOKEN","PIPEDREAM_SOURCE_ID")get_requestbin_history(pipedream_token: str, source_id: str)pipedream_token: Pipedream API tokensource_id: Pipedream source IDYou can use requestbin ofpipedreamas attacker server.Browser Simulator OperationBrowserSimulatorExecute SSO Flowsso_flow=f"""page.locator('input[name="username"]').fill('{victim_username}')page.locator('input[name="password"]').fill('{victim_password}')page.locator('button[type="submit"]').click()print(page.content())"""bs.run(sso_flow)bs.run(script: str)script: Script that shows browser behavior for executing SSO flow, written in PlayWright.Visit the specified URLbs.visit("https://example.com")bs.visit(url: str)url: URL to visit in browser.Retrieving the content displayed in the browserbs.get_content()bs.get_content() -> strClose the browserbs.close()bs.close()CLI OperationOsbtlib.clisend test result to osbt serverosbt.cli.send_result("IDSpoofing","- The attacker op modifies the id_token to impersonate the victim <br> - The sub claim of the id_token is modified to the victim's sub claim","Passed","","- Check the signature of the id_token <br> - Check the iss claim of the id_token <br> - Check the sub claim of the id_token")send_result(test_name: str, description: str, outcome: str, err_msg: str, countermeasure: str) -> dictOthersID Token OperationOsbtlib.id_tokenget, replace headerosbt.id_token.get_header("[id_token]")# {'alg': 'HS256', 'typ': 'JWT'}osbt.id_token.replace_header("[id_token]",{'alg':'HS256','typ':'JWS'})get_header(id_token: str) -> dictid_token: ID token for which the header will be obtained.replace_header(id_token: str, new_header: dict) -> strid_token: ID token for which the header will be replaced.new_header: Header of ID token after replacement.get, replace payloadosbt.id_token.get_payload("[id_token]")# {'sub': '1234', 'username': 'guest'}osbt.id_token.replace_payload("[id_token]",{'sub':'1234','username':'guest'})get_payload(id_token: str) -> dictid_token: ID token for which the payload will be obtained.replace_payload(id_token: str, new_payload: dict) -> strid_token: ID token for which the payload will be replaced.new_payload: Payload of ID token after replacement.get, replace signatureosbt.id_token.get_signature("[id_token]")# SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cosbt.id_token.replace_header("[id_token]","SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")get_signature(id_token: str) -> strid_token: ID token for which the signature will be obtained.replace_header(id_token: str, new_signature: str) -> strid_token: ID token for which the signature will be replaced.new_signature: Header of ID token after replacement.