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

This commit is contained in:
Translator
2025-05-09 11:20:06 +00:00
parent 602587864e
commit 07000a6944
7 changed files with 415 additions and 781 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:
Για περισσότερες πληροφορίες σχετικά με τις υπηρεσίες Azure App, ελέγξτε:
{{#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**:
Αυτές οι άδειες επιτρέπουν την πρόσβαση σε ένα **SSH shell** μέσα σε μια εφαρμογή ιστού. Επιτρέπουν επίσης την **αποσφαλμάτωση** της εφαρμογής.
- **SSH σε μία εντολή**:
```bash
# Direct option
az webapp ssh --name <name> --resource-group <res-group>
```
- **Create tunnel and then connect to SSH**:
- **Δημιουργήστε σήραγγα και στη συνέχεια συνδεθείτε μέσω 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
```
- **Αποσφαλμάτωση της εφαρμογής**:
1. Εγκαταστήστε την επέκταση Azure στο VScode.
2. Συνδεθείτε στην επέκταση με τον λογαριασμό Azure.
3. Καταγράψτε όλες τις υπηρεσίες App μέσα στη συνδρομή.
4. Επιλέξτε την υπηρεσία App που θέλετε να αποσφαλματώσετε, κάντε δεξί κλικ και επιλέξτε "Start Debugging".
5. Εάν η εφαρμογή δεν έχει ενεργοποιημένη την αποσφαλμάτωση, η επέκταση θα προσπαθήσει να την ενεργοποιήσει, αλλά ο λογαριασμός σας χρειάζεται την άδεια `Microsoft.Web/sites/config/write` για να το κάνει αυτό.
- **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.
### Απόκτηση Διαπιστευτηρίων SCM & Ενεργοποίηση Βασικής Αυθεντικοποίησης
### 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:
Για να αποκτήσετε τα διαπιστευτήρια SCM, μπορείτε να χρησιμοποιήσετε τις παρακάτω **εντολές και άδειες**:
- Η άδεια **`Microsoft.Web/sites/publishxml/action`** επιτρέπει την κλήση:
```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"
}
]
```
Σημειώστε ότι το **όνομα χρήστη είναι πάντα το ίδιο** (εκτός από το FTP που προσθέτει το όνομα της εφαρμογής στην αρχή) αλλά ο **κωδικός πρόσβασης είναι ο ίδιος** για όλους αυτούς.
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:
Επιπλέον, το **SCM URL είναι `<app-name>.scm.azurewebsites.net`**.
- Η άδεια **`Microsoft.Web/sites/config/list/action`** επιτρέπει την κλήση:
```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"
}
```
Σημειώστε ότι τα **διαπιστευτήρια είναι τα ίδια** με την προηγούμενη εντολή.
Note how the **credentials are the same** as in the previous command.
- Another option would be to **set you own creds** and use them:
- Μια άλλη επιλογή θα ήταν να **ορίσετε τα δικά σας διαπιστευτήρια** και να τα χρησιμοποιήσετε:
```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
```
Τότε, μπορείτε να χρησιμοποιήσετε αυτά τα διαπιστευτήρια για να **πρόσβαση στις πλατφόρμες SCM και FTP**. Αυτός είναι επίσης ένας εξαιρετικός τρόπος για να διατηρήσετε την επιμονή.
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`**.
Θυμηθείτε ότι για να αποκτήσετε πρόσβαση στην πλατφόρμα SCM από το **web πρέπει να αποκτήσετε πρόσβαση στο `<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`):
> Σημειώστε ότι κάθε χρήστης μπορεί να διαμορφώσει τα δικά του διαπιστευτήρια καλώντας την προηγούμενη εντολή, αλλά αν ο χρήστης δεν έχει αρκετές άδειες για να αποκτήσει πρόσβαση στο SCM ή FTP, τα διαπιστευτήρια δεν θα λειτουργήσουν.
- Αν δείτε ότι αυτά τα διαπιστευτήρια είναι **REDACTED**, είναι επειδή **πρέπει να ενεργοποιήσετε την επιλογή βασικής αυθεντικοποίησης SCM** και για αυτό χρειάζεστε τη δεύτερη άδεια (`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
}
}'
```
### Δημοσίευση κώδικα χρησιμοποιώντας διαπιστευτήρια SCM
### Publish code using SCM credentials
Απλά έχοντας έγκυρα διαπιστευτήρια SCM είναι δυνατό να **δημοσιεύσετε κώδικα** στην υπηρεσία App. Αυτό μπορεί να γίνει χρησιμοποιώντας την παρακάτω εντολή.
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:
Για αυτό το παράδειγμα python μπορείτε να κατεβάσετε το repo από https://github.com/Azure-Samples/msdocs-python-flask-webapp-quickstart, να κάνετε οποιεσδήποτε **αλλαγές** επιθυμείτε και στη συνέχεια να **συμπιέσετε τρέχοντας: `zip -r app.zip .`**.
Στη συνέχεια, μπορείτε να **δημοσιεύσετε τον κώδικα** σε μια διαδικτυακή εφαρμογή με την παρακάτω εντολή:
```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 credentials
The mentioned Azure permission allows to perform several interesting actions that can also be performed with the SCM credentials:
- Read **Webjobs** logs:
Η αναφερόμενη άδεια Azure επιτρέπει την εκτέλεση αρκετών ενδιαφερόντων ενεργειών που μπορούν επίσης να εκτελούνται με τα διαπιστευτήρια SCM:
- Διαβάστε τα **Webjobs** logs:
```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:
- Διαβάστε τον πηγαίο κώδικα **Webjobs**:
```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**:
- Δημιουργήστε **συνεχή 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.
Αυτές οι άδειες επιτρέπουν να **ανατεθεί μια διαχειριζόμενη ταυτότητα** στην υπηρεσία App, οπότε αν μια υπηρεσία App είχε προηγουμένως παραβιαστεί, αυτό θα επιτρέψει στον επιτιθέμενο να αναθέσει νέες διαχειριζόμενες ταυτότητες στην υπηρεσία App και να **κλιμακώσει τα δικαιώματα** σε αυτές.
```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.
Αυτή η άδεια επιτρέπει την καταγραφή των **connection strings** και των **appsettings** της υπηρεσίας App, τα οποία μπορεί να περιέχουν ευαίσθητες πληροφορίες όπως διαπιστευτήρια βάσης δεδομένων.
```bash
az webapp config connection-string list --name <name> --resource-group <res-group>
az webapp config appsettings list --name <name> --resource-group <res-group>
```
### Διαβάστε τις Ρυθμισμένες Πιστοποιήσεις Τρίτων
### 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.
Εκτελώντας την παρακάτω εντολή, είναι δυνατόν να **διαβάσετε τις πιστοποιήσεις τρίτων** που είναι ρυθμισμένες στον τρέχοντα λογαριασμό. Σημειώστε ότι αν για παράδειγμα κάποιες πιστοποιήσεις Github είναι ρυθμισμένες σε διαφορετικό χρήστη, δεν θα μπορείτε να αποκτήσετε πρόσβαση στο token από έναν διαφορετικό.
```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"
```
Αυτή η εντολή επιστρέφει διακριτικά για το Github, Bitbucket, Dropbox και OneDrive.
This command returns tokens for Github, Bitbucket, Dropbox and OneDrive.
Here you have some command examples to check the tokens:
Εδώ έχετε μερικά παραδείγματα εντολών για να ελέγξετε τα διακριτικά:
```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
```
### Ενημέρωση Κώδικα Εφαρμογής από την πηγή
### 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>`
- Εάν η ρυθμισμένη πηγή είναι ένας τρίτος πάροχος όπως το Github, το BitBucket ή ένα Azure Repository, μπορείτε να **ενημερώσετε τον κώδικα** της υπηρεσίας εφαρμογής παραβιάζοντας τον πηγαίο κώδικα στο αποθετήριο.
- Εάν η εφαρμογή είναι ρυθμισμένη να χρησιμοποιεί ένα **απομακρυσμένο git repository** (με όνομα χρήστη και κωδικό πρόσβασης), είναι δυνατόν να αποκτήσετε το **URL και τα βασικά διαπιστευτήρια αυθεντικοποίησης** για να κλωνοποιήσετε και να σπρώξετε αλλαγές με:
- Χρησιμοποιώντας την άδεια **`Microsoft.Web/sites/sourcecontrols/read`**: `az webapp deployment source show --name <app-name> --resource-group <res-group>`
- Χρησιμοποιώντας την άδεια **`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"`
- Εάν η εφαρμογή είναι ρυθμισμένη να χρησιμοποιεί ένα **τοπικό git repository**, είναι δυνατόν να **κλωνοποιήσετε το αποθετήριο** και να **σπρώξετε αλλαγές** σε αυτό:
- Χρησιμοποιώντας την άδεια **`Microsoft.Web/sites/sourcecontrols/read`**: Μπορείτε να αποκτήσετε το URL του git repo με `az webapp deployment source show --name <app-name> --resource-group <res-group>`, αλλά θα είναι το ίδιο με το SCM URL της εφαρμογής με τη διαδρομή `/<app-name>.git` (π.χ. `https://pythonwebapp-audeh9f5fzeyhhed.scm.canadacentral-01.azurewebsites.net:443/pythonwebapp.git`).
- Για να αποκτήσετε τα διαπιστευτήρια SCM χρειάζεστε την άδεια:
- **`Microsoft.Web/sites/publishxml/action`**: Στη συνέχεια, εκτελέστε `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>`.
- **`Microsoft.Web/sites/config/list/action`**: Στη συνέχεια, εκτελέστε `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.
> Σημειώστε ότι έχοντας την άδεια `Microsoft.Web/sites/config/list/action` και τα διαπιστευτήρια SCM είναι πάντα δυνατό να αναπτύξετε σε μια webapp (ακόμα και αν είχε ρυθμιστεί να χρησιμοποιεί έναν τρίτο πάροχο) όπως αναφέρθηκε σε προηγούμενη ενότητα.
> [!WARNING]
> Note that having the permissions below it's also **possible to execute an arbitrary container** even if the webapp was configured differently.
> Σημειώστε ότι έχοντας τις παρακάτω άδειες είναι επίσης **δυνατό να εκτελέσετε ένα αυθαίρετο κοντέινερ** ακόμα και αν η webapp είχε ρυθμιστεί διαφορετικά.
### `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.
Αυτό είναι το σύνολο αδειών που επιτρέπει να **τροποποιήσετε το κοντέινερ που χρησιμοποιείται** από μια webapp. Ένας επιτιθέμενος θα μπορούσε να το εκμεταλλευτεί για να κάνει μια webapp να εκτελεί ένα κακόβουλο κοντέινερ.
```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

@@ -4,7 +4,7 @@
## App Service Basic Information
Azure App Services επιτρέπει στους προγραμματιστές να **δημιουργούν, αναπτύσσουν και κλιμακώνουν διαδικτυακές εφαρμογές, backend εφαρμογών κινητών και APIs χωρίς προβλήματα**. Υποστηρίζει πολλές γλώσσες προγραμματισμού και ενσωματώνεται με διάφορα εργαλεία και υπηρεσίες Azure για βελτιωμένη λειτουργικότητα και διαχείριση.
Azure App Services επιτρέπει στους προγραμματιστές να **δημιουργούν, αναπτύσσουν και κλιμακώνουν διαδικτυακές εφαρμογές, backend κινητών εφαρμογών και APIs χωρίς προβλήματα**. Υποστηρίζει πολλές γλώσσες προγραμματισμού και ενσωματώνεται με διάφορα εργαλεία και υπηρεσίες Azure για βελτιωμένη λειτουργικότητα και διαχείριση.
Κάθε εφαρμογή εκτελείται μέσα σε ένα sandbox, αλλά η απομόνωση εξαρτάται από τα σχέδια App Service:
@@ -13,18 +13,19 @@ Azure App Services επιτρέπει στους προγραμματιστές
- Τα απομονωμένα επίπεδα εκτελούνται σε **αφιερωμένες VM σε αφιερωμένα εικονικά δίκτυα**, βελτιώνοντας την απομόνωση των εφαρμογών.
> [!WARNING]
> Σημειώστε ότι **κανένα** από αυτά τα επίπεδα απομόνωσης **δεν αποτρέπει** άλλες κοινές **ευπάθειες ιστού** (όπως η μεταφόρτωση αρχείων ή οι εισβολές). Και αν χρησιμοποιηθεί μια **ταυτότητα διαχείρισης**, θα μπορούσε να είναι σε θέση να **κλιμακώσει τα δικαιώματα σε αυτές**.
> Σημειώστε ότι **κανένα** από αυτά τα επίπεδα απομόνωσης **δεν αποτρέπει** άλλες κοινές **ευπάθειες ιστού** (όπως η μεταφόρτωση αρχείων ή οι εγχύσεις). Και αν χρησιμοποιηθεί μια **ταυτότητα διαχείρισης**, θα μπορούσε να είναι σε θέση να **κλιμακώσει τα δικαιώματα σε αυτές**.
Οι εφαρμογές έχουν κάποιες ενδιαφέρουσες ρυθμίσεις:
- **Always On**: Διασφαλίζει ότι η εφαρμογή είναι πάντα σε λειτουργία. Αν δεν είναι ενεργοποιημένο, η εφαρμογή θα σταματήσει να λειτουργεί μετά από 20 λεπτά αδράνειας και θα ξεκινήσει ξανά όταν ληφθεί ένα αίτημα.
- Αυτό είναι απαραίτητο αν έχετε μια webjob που χρειάζεται να εκτελείται συνεχώς, καθώς η webjob θα σταματήσει αν η εφαρμογή σταματήσει.
- Αυτό είναι απαραίτητο αν έχετε μια webjob που χρειάζεται να εκτελείται συνεχώς, καθώς η webjob θα σταματήσει αν σταματήσει η εφαρμογή.
- **SSH**: Αν είναι ενεργοποιημένο, ένας χρήστης με αρκετά δικαιώματα μπορεί να συνδεθεί στην εφαρμογή χρησιμοποιώντας SSH.
- **Debugging**: Αν είναι ενεργοποιημένο, ένας χρήστης με αρκετά δικαιώματα μπορεί να αποσφαλματώσει την εφαρμογή. Ωστόσο, αυτό απενεργοποιείται αυτόματα κάθε 48 ώρες.
- **Web App + Database**: Η διαδικτυακή κονσόλα επιτρέπει τη δημιουργία μιας εφαρμογής με μια βάση δεδομένων. Σε αυτή την περίπτωση, είναι δυνατή η επιλογή της βάσης δεδομένων που θα χρησιμοποιηθεί (SQLAzure, PostgreSQL, MySQL, MongoDB) και επιτρέπει επίσης τη δημιουργία ενός Azure Cache for Redis.
- Η διεύθυνση URL που περιέχει τα διαπιστευτήρια για τη βάση δεδομένων και το Redis θα αποθηκευτεί στα **appsettings**.
- **Container**: Είναι δυνατή η ανάπτυξη ενός container στην υπηρεσία App Service υποδεικνύοντας τη διεύθυνση URL του container και τα διαπιστευτήρια για την πρόσβαση σε αυτό.
- **Mounts**: Είναι δυνατή η δημιουργία 5 mounts από λογαριασμούς αποθήκευσης, είτε αυτοί είναι Azure Blob (Μόνο για Ανάγνωση) είτε Azure Files. Η ρύθμιση θα αποθηκεύσει το κλειδί πρόσβασης πάνω από τον λογαριασμό αποθήκευσης.
- **Networking**: Μπορεί να είναι δημόσια διαθέσιμη ή προσβάσιμη μόνο μέσω ιδιωτικών endpoints από ένα VNet.
## Basic Authentication
@@ -39,7 +40,7 @@ Azure App Services επιτρέπει στους προγραμματιστές
### Kudu
Kudu είναι η πλατφόρμα που **διαχειρίζεται τόσο το SCM όσο και μια διαδικτυακή και API διεπαφή** για τη διαχείριση μιας υπηρεσίας App Service, και παρέχει δυνατότητες ανάπτυξης βασισμένες σε Git, απομακρυσμένη αποσφαλμάτωση και διαχείριση αρχείων. Είναι προσβάσιμη μέσω της διεύθυνσης URL SCM που ορίζεται στην διαδικτυακή εφαρμογή.
Το Kudu είναι η πλατφόρμα που **διαχειρίζεται τόσο το SCM όσο και μια διαδικτυακή και API διεπαφή** για τη διαχείριση μιας υπηρεσίας App Service, και παρέχει δυνατότητες ανάπτυξης βασισμένες σε Git, απομακρυσμένη αποσφαλμάτωση και διαχείριση αρχείων. Είναι προσβάσιμο μέσω της διεύθυνσης URL SCM που ορίζεται στην διαδικτυακή εφαρμογή.
Σημειώστε ότι οι εκδόσεις Kudu που χρησιμοποιούνται από τις υπηρεσίες App Services και από τις Function Apps είναι διαφορετικές, με την έκδοση των Function apps να είναι πολύ πιο περιορισμένη.
@@ -56,7 +57,7 @@ Kudu είναι η πλατφόρμα που **διαχειρίζεται τόσ
## Sources
Οι υπηρεσίες App Services επιτρέπουν την ανέβασμα του κώδικα ως αρχείο zip από προεπιλογή, αλλά επιτρέπουν επίσης τη σύνδεση σε μια τρίτη υπηρεσία και την απόκτηση του κώδικα από εκεί.
Οι υπηρεσίες App Services επιτρέπουν την αποστολή του κώδικα ως αρχείο zip από προεπιλογή, αλλά επιτρέπουν επίσης τη σύνδεση σε μια τρίτη υπηρεσία και την απόκτηση του κώδικα από εκεί.
- Οι τρέχουσες υποστηριζόμενες τρίτες πηγές είναι **Github** και **Bitbucket**.
- Μπορείτε να αποκτήσετε τα διαπιστευτήρια αυθεντικοποίησης εκτελώντας `az rest --url "https://management.azure.com/providers/Microsoft.Web/sourcecontrols?api-version=2024-04-01"`
@@ -70,10 +71,10 @@ Kudu είναι η πλατφόρμα που **διαχειρίζεται τόσ
## Webjobs
Οι Azure WebJobs είναι **εργασίες φόντου που εκτελούνται στο περιβάλλον Azure App Service**. Επιτρέπουν στους προγραμματιστές να εκτελούν σενάρια ή προγράμματα παράλληλα με τις διαδικτυακές τους εφαρμογές, διευκολύνοντας την εκτέλεση ασύγχρονων ή χρονοβόρων εργασιών όπως η επεξεργασία αρχείων, η διαχείριση δεδομένων ή οι προγραμματισμένες εργασίες.
Οι Azure WebJobs είναι **εργασίες φόντου που εκτελούνται στο περιβάλλον Azure App Service**. Επιτρέπουν στους προγραμματιστές να εκτελούν σενάρια ή προγράμματα παράλληλα με τις διαδικτυακές τους εφαρμογές, διευκολύνοντας τη διαχείριση ασύγχρονων ή χρονοβόρων εργασιών όπως η επεξεργασία αρχείων, η διαχείριση δεδομένων ή οι προγραμματισμένες εργασίες.
Υπάρχουν 2 τύποι web jobs:
- **Continuous**: Εκτελείται επ' αόριστον σε βρόχο και ενεργοποιείται μόλις δημιουργηθεί. Είναι ιδανικό για εργασίες που απαιτούν συνεχή επεξεργασία. Ωστόσο, αν η εφαρμογή σταματήσει να λειτουργεί επειδή το Always On είναι απενεργοποιημένο και δεν έχει λάβει αίτημα τα τελευταία 20 λεπτά, η web job θα σταματήσει επίσης.
- **Triggered**: Εκτελείται κατόπιν αιτήματος ή με βάση ένα πρόγραμμα. Είναι πιο κατάλληλο για περιοδικές εργασίες, όπως ενημερώσεις δεδομένων batch ή ρουτίνες συντήρησης.
- **Continuous**: Εκτελείται αδιάκοπα σε βρόχο και ενεργοποιείται μόλις δημιουργηθεί. Είναι ιδανικό για εργασίες που απαιτούν συνεχή επεξεργασία. Ωστόσο, αν η εφαρμογή σταματήσει να λειτουργεί επειδή το Always On είναι απενεργοποιημένο και δεν έχει λάβει αίτημα τα τελευταία 20 λεπτά, η web job θα σταματήσει επίσης.
- **Triggered**: Εκτελείται κατόπιν αιτήματος ή με βάση ένα πρόγραμμα. Είναι καλύτερα προσαρμοσμένο για περιοδικές εργασίες, όπως ενημερώσεις δεδομένων batch ή ρουτίνες συντήρησης.
Οι webjobs είναι πολύ ενδιαφέρουσες από την προοπτική των επιτιθέμενων, καθώς θα μπορούσαν να χρησιμοποιηθούν για **εκτέλεση κώδικα** στο περιβάλλον και **κλιμάκωση δικαιωμάτων** στις συνδεδεμένες διαχειριζόμενες ταυτότητες.
@@ -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 }}
@@ -283,7 +288,7 @@ cd msdocs-python-flask-webapp-quickstart
# Create webapp from this code
az webapp up --runtime PYTHON:3.9 --sku B1 --logs
```
Συνδεόμενος στο SCM portal ή συνδεόμενος μέσω FTP, είναι δυνατόν να δει κανείς στο `/wwwroot` το συμπιεσμένο αρχείο `output.tar.gz` που περιέχει τον κώδικα της webapp.
Συνδεόμενοι στο SCM portal ή συνδεόμενοι μέσω FTP, είναι δυνατόν να δούμε στο `/wwwroot` το συμπιεσμένο αρχείο `output.tar.gz` που περιέχει τον κώδικα της webapp.
> [!TIP]
> Απλώς η σύνδεση μέσω FTP και η τροποποίηση του αρχείου `output.tar.gz` δεν είναι αρκετή για να αλλάξει τον κώδικα που εκτελείται από την webapp.
@@ -298,7 +303,7 @@ az webapp up --runtime PYTHON:3.9 --sku B1 --logs
2. Δημιουργήστε μια νέα python Web App στο Azure.
3. Στο `Deployment Center` αλλάξτε την πηγή, συνδεθείτε με το Github, επιλέξτε το forked repo και κάντε κλικ στο `Save`.
Όπως και στην προηγούμενη περίπτωση, συνδεόμενος στο SCM portal ή συνδεόμενος μέσω FTP, είναι δυνατόν να δει κανείς στο `/wwwroot` το συμπιεσμένο αρχείο `output.tar.gz` που περιέχει τον κώδικα της webapp.
Όπως και στην προηγούμενη περίπτωση, συνδεόμενοι στο SCM portal ή συνδεόμενοι μέσω FTP, είναι δυνατόν να δούμε στο `/wwwroot` το συμπιεσμένο αρχείο `output.tar.gz` που περιέχει τον κώδικα της webapp.
> [!TIP]
> Απλώς η σύνδεση μέσω FTP και η τροποποίηση του αρχείου `output.tar.gz` και η επανενεργοποίηση μιας ανάπτυξης δεν είναι αρκετή για να αλλάξει τον κώδικα που εκτελείται από την webapp.

View File

@@ -4,79 +4,81 @@
## Basic Information
**Azure Function Apps** είναι μια **serverless compute service** που σας επιτρέπει να εκτελείτε μικρά κομμάτια κώδικα, που ονομάζονται **functions**, χωρίς να διαχειρίζεστε την υποκείμενη υποδομή. Είναι σχεδιασμένα να εκτελούν κώδικα σε απάντηση σε διάφορους ενεργοποιητές, όπως **HTTP requests, timers, ή events από άλλες υπηρεσίες Azure** όπως Blob Storage ή Event Hubs. Τα Function Apps υποστηρίζουν πολλές γλώσσες προγραμματισμού, συμπεριλαμβανομένων των C#, Python, JavaScript και Java, καθιστώντας τα ευέλικτα για την κατασκευή **event-driven applications**, αυτοματοποίηση ροών εργασίας ή ενσωμάτωση υπηρεσιών. Είναι οικονομικά, καθώς συνήθως πληρώνετε μόνο για τον χρόνο υπολογισμού που χρησιμοποιείται όταν εκτελείται ο κώδικάς σας.
**Azure Function Apps** είναι μια **υπηρεσία υπολογισμού χωρίς διακομιστές** που σας επιτρέπει να εκτελείτε μικρά κομμάτια κώδικα, που ονομάζονται **functions**, χωρίς να διαχειρίζεστε την υποκείμενη υποδομή. Είναι σχεδιασμένα να εκτελούν κώδικα σε απάντηση σε διάφορους ενεργοποιητές, όπως **HTTP requests, timers, ή events από άλλες υπηρεσίες Azure** όπως το Blob Storage ή το Event Hubs. Οι Function Apps υποστηρίζουν πολλές γλώσσες προγραμματισμού, συμπεριλαμβανομένων των C#, Python, JavaScript και Java, καθιστώντας τις ευέλικτες για την κατασκευή **event-driven applications**, αυτοματοποίηση ροών εργασίας ή ενσωμάτωσης υπηρεσιών. Είναι οικονομικές, καθώς συνήθως πληρώνετε μόνο για τον χρόνο υπολογισμού που χρησιμοποιείται όταν εκτελείται ο κώδικάς σας.
> [!NOTE]
> Σημειώστε ότι **Functions είναι ένα υποσύνολο των App Services**, επομένως, πολλές από τις δυνατότητες που συζητούνται εδώ θα χρησιμοποιηθούν επίσης από εφαρμογές που δημιουργούνται ως Azure Apps (`webapp` στο cli).
### Different Plans
- **Flex Consumption Plan**: Προσφέρει **dynamic, event-driven scaling** με τιμολόγηση pay-as-you-go, προσθέτοντας ή αφαιρώντας παραδείγματα λειτουργιών με βάση τη ζήτηση. Υποστηρίζει **virtual networking** και **pre-provisioned instances** για να μειώσει τις κρύες εκκινήσεις, καθιστώντας το κατάλληλο για **variable workloads** που δεν απαιτούν υποστήριξη κοντέινερ.
- **Traditional Consumption Plan**: Η προεπιλεγμένη serverless επιλογή, όπου **πληρώνετε μόνο για τους υπολογιστικούς πόρους όταν εκτελούνται οι λειτουργίες**. Αυξάνεται αυτόματα με βάση τα εισερχόμενα γεγονότα και περιλαμβάνει **cold start optimizations**, αλλά δεν υποστηρίζει αναπτύξεις κοντέινερ. Ιδανικό για **intermittent workloads** που απαιτούν αυτόματη κλιμάκωση.
- **Premium Plan**: Σχεδιασμένο για **consistent performance**, με **prewarmed workers** για να εξαλείψει τις κρύες εκκινήσεις. Προσφέρει **extended execution times, virtual networking**, και υποστηρίζει **custom Linux images**, καθιστώντας το ιδανικό για **mission-critical applications** που χρειάζονται υψηλή απόδοση και προηγμένες δυνατότητες.
- **Dedicated Plan**: Λειτουργεί σε αφιερωμένες εικονικές μηχανές με **predictable billing** και υποστηρίζει χειροκίνητη ή αυτόματη κλιμάκωση. Επιτρέπει την εκτέλεση πολλών εφαρμογών στο ίδιο σχέδιο, παρέχει **compute isolation**, και εξασφαλίζει **secure network access** μέσω App Service Environments, καθιστώντας το ιδανικό για **long-running applications** που χρειάζονται συνεπή κατανομή πόρων.
- **Container Apps**: Επιτρέπει την ανάπτυξη **containerized function apps** σε ένα διαχειριζόμενο περιβάλλον, παράλληλα με μικροϋπηρεσίες και APIs. Υποστηρίζει προσαρμοσμένες βιβλιοθήκες, μετανάστευση κληρονομικών εφαρμογών, και **GPU processing**, εξαλείφοντας τη διαχείριση του Kubernetes cluster. Ιδανικό για **event-driven, scalable containerized applications**.
- **Flex Consumption Plan**: Προσφέρει **δυναμική, event-driven κλιμάκωση** με τιμολόγηση pay-as-you-go, προσθέτοντας ή αφαιρώντας παραδείγματα λειτουργιών με βάση τη ζήτηση. Υποστηρίζει **virtual networking** και **προετοιμασμένα παραδείγματα** για να μειώσει τις κρύες εκκινήσεις, καθιστώντας το κατάλληλο για **μεταβλητά φορτία εργασίας** που δεν απαιτούν υποστήριξη κοντέινερ.
- **Traditional Consumption Plan**: Η προεπιλεγμένη επιλογή χωρίς διακομιστές, όπου **πληρώνετε μόνο για τους υπολογιστικούς πόρους όταν εκτελούνται οι λειτουργίες**. Κλιμακώνει αυτόματα με βάση τα εισερχόμενα γεγονότα και περιλαμβάνει **βελτιστοποιήσεις κρύας εκκίνησης**, αλλά δεν υποστηρίζει αναπτύξεις κοντέινερ. Ιδανικό για **διαλείποντα φορτία εργασίας** που απαιτούν αυτόματη κλιμάκωση.
- **Premium Plan**: Σχεδιασμένο για **σταθερή απόδοση**, με **προθερμασμένους εργαζόμενους** για να εξαλείψει τις κρύες εκκινήσεις. Προσφέρει **εκτεταμένους χρόνους εκτέλεσης, virtual networking**, και υποστηρίζει **προσαρμοσμένες εικόνες Linux**, καθιστώντας το τέλειο για **εφαρμογές κρίσιμης σημασίας** που χρειάζονται υψηλή απόδοση και προηγμένες δυνατότητες.
- **Dedicated Plan**: Λειτουργεί σε αφιερωμένες εικονικές μηχανές με **προβλέψιμο τιμολόγιο** και υποστηρίζει χειροκίνητη ή αυτόματη κλιμάκωση. Επιτρέπει την εκτέλεση πολλών εφαρμογών στο ίδιο σχέδιο, παρέχει **απομόνωση υπολογισμού**, και εξασφαλίζει **ασφαλή πρόσβαση στο δίκτυο** μέσω App Service Environments, καθιστώντας το ιδανικό για **μακροχρόνιες εφαρμογές** που χρειάζονται συνεπή κατανομή πόρων.
- **Container Apps**: Επιτρέπει την ανάπτυξη **containerized function apps** σε ένα διαχειριζόμενο περιβάλλον, παράλληλα με μικροϋπηρεσίες και APIs. Υποστηρίζει προσαρμοσμένες βιβλιοθήκες, μετανάστευση κληρονομημένων εφαρμογών, και **GPU processing**, εξαλείφοντας τη διαχείριση του Kubernetes cluster. Ιδανικό για **event-driven, scalable containerized applications**.
### **Storage Buckets**
Όταν δημιουργείτε μια νέα Function App που δεν είναι κοντενέιζερ (αλλά δίνετε τον κώδικα για εκτέλεση), τα **δεδομένα κώδικα και άλλα σχετικά με τη Function θα αποθηκευτούν σε έναν λογαριασμό Storage**. Από προεπιλογή, η διαδικτυακή κονσόλα θα δημιουργήσει έναν νέο ανά λειτουργία για να αποθηκεύσει τον κώδικα.
Όταν δημιουργείτε μια νέα Function App που δεν είναι κοντενιέ, το **κώδικα και άλλα δεδομένα που σχετίζονται με τη Function θα αποθηκευτούν σε έναν λογαριασμό Storage**. Από προεπιλογή, η διαδικτυακή κονσόλα θα δημιουργήσει έναν νέο ανά λειτουργία για να αποθηκεύσει τον κώδικα.
Επιπλέον, τροποποιώντας τον κώδικα μέσα στο bucket (στα διάφορα φορμά που μπορεί να αποθηκευτεί), ο **κώδικας της εφαρμογής θα τροποποιηθεί στον νέο και θα εκτελείται** την επόμενη φορά που θα κληθεί η Function.
> [!CAUTION]
> Αυτό είναι πολύ ενδιαφέρον από την προοπτική ενός επιτιθέμενου καθώς η **πρόσβαση εγγραφής σε αυτό το bucket** θα επιτρέψει σε έναν επιτιθέμενο να **συμβιβάσει τον κώδικα και να κλιμακώσει τα προνόμια** στις διαχειριζόμενες ταυτότητες μέσα στην Function App.
> Αυτό είναι πολύ ενδιαφέρον από την προοπτική ενός επιτιθέμενου, καθώς η **πρόσβαση εγγραφής σε αυτό το bucket** θα επιτρέψει σε έναν επιτιθέμενο να **συμβιβάσει τον κώδικα και να κλιμακώσει τα δικαιώματα** στις διαχειριζόμενες ταυτότητες μέσα στην Function App.
>
> Περισσότερα σχετικά με αυτό στην **ενότητα κλιμάκωσης προνομίων**.
> Περισσότερα σχετικά με αυτό στην **ενότητα κλιμάκωσης δικαιωμάτων**.
Είναι επίσης δυνατό να βρείτε τα **master και functions keys** αποθηκευμένα στον λογαριασμό storage στο κοντέινερ **`azure-webjobs-secrets`** μέσα στον φάκελο **`<app-name>`** στα αρχεία JSON που μπορείτε να βρείτε μέσα.
Σημειώστε ότι οι Functions επιτρέπουν επίσης την αποθήκευση του κώδικα σε μια απομακρυσμένη τοποθεσία απλά υποδεικνύοντας το URL της.
Σημειώστε ότι οι Functions επιτρέπουν επίσης την αποθήκευση του κώδικα σε μια απομακρυσμένη τοποθεσία απλά υποδεικνύοντας το URL σε αυτήν.
### Networking
Χρησιμοποιώντας έναν HTTP trigger:
- Είναι δυνατό να δώσετε **πρόσβαση σε μια λειτουργία από όλο το Διαδίκτυο** χωρίς να απαιτείται καμία πιστοποίηση ή να δώσετε πρόσβαση με βάση το IAM. Αν και είναι επίσης δυνατό να περιορίσετε αυτή την πρόσβαση.
- Είναι δυνατό να δώσετε **πρόσβαση σε μια λειτουργία από όλο το Διαδίκτυο** χωρίς να απαιτείται καμία πιστοποίηση ή να δώσετε πρόσβαση με βάση το IAM. Αν και είναι επίσης δυνατό να περιορίσετε αυτήν την πρόσβαση.
- Είναι επίσης δυνατό να **δώσετε ή να περιορίσετε την πρόσβαση** σε μια Function App από **ένα εσωτερικό δίκτυο (VPC)**.
> [!CAUTION]
> Αυτό είναι πολύ ενδιαφέρον από την προοπτική ενός επιτιθέμενου καθώς μπορεί να είναι δυνατό να **pivot σε εσωτερικά δίκτυα** από μια ευάλωτη Function που είναι εκτεθειμένη στο Διαδίκτυο.
> Αυτό είναι πολύ ενδιαφέρον από την προοπτική ενός επιτιθέμενου, καθώς μπορεί να είναι δυνατό να **περάσετε σε εσωτερικά δίκτυα** από μια ευάλωτη Function που εκτίθεται στο Διαδίκτυο.
### **Function App Settings & Environment Variables**
Είναι δυνατό να ρυθμίσετε μεταβλητές περιβάλλοντος μέσα σε μια εφαρμογή, οι οποίες μπορεί να περιέχουν ευαίσθητες πληροφορίες. Επιπλέον, από προεπιλογή οι env μεταβλητές **`AzureWebJobsStorage`** και **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (μεταξύ άλλων) δημιουργούνται. Αυτές είναι ιδιαίτερα ενδιαφέρουσες γιατί **περιέχουν το κλειδί λογαριασμού για τον έλεγχο με ΠΛΗΡΗ δικαιώματα του λογαριασμού storage που περιέχει τα δεδομένα της εφαρμογής**. Αυτές οι ρυθμίσεις είναι επίσης απαραίτητες για την εκτέλεση του κώδικα από τον Λογαριασμό Storage.
Είναι δυνατό να ρυθμίσετε μεταβλητές περιβάλλοντος μέσα σε μια εφαρμογή, οι οποίες μπορεί να περιέχουν ευαίσθητες πληροφορίες. Επιπλέον, από προεπιλογή οι μεταβλητές περιβάλλοντος **`AzureWebJobsStorage`** και **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (μεταξύ άλλων) δημιουργούνται. Αυτές είναι ιδιαίτερα ενδιαφέρουσες γιατί **περιέχουν το κλειδί λογαριασμού για τον έλεγχο με ΠΛΗΡΗ δικαιώματα του λογαριασμού storage που περιέχει τα δεδομένα της εφαρμογής**. Αυτές οι ρυθμίσεις είναι επίσης απαραίτητες για την εκτέλεση του κώδικα από τον Λογαριασμό Storage.
Αυτές οι env μεταβλητές ή παράμετροι ρύθμισης ελέγχουν επίσης πώς η Function εκτελεί τον κώδικα, για παράδειγμα αν **`WEBSITE_RUN_FROM_PACKAGE`** υπάρχει, θα υποδεικνύει το URL όπου βρίσκεται ο κώδικας της εφαρμογής.
Αυτές οι μεταβλητές περιβάλλοντος ή παράμετροι ρύθμισης ελέγχουν επίσης πώς η Function εκτελεί τον κώδικα, για παράδειγμα, αν υπάρχει **`WEBSITE_RUN_FROM_PACKAGE`**, θα υποδεικνύει το URL όπου βρίσκεται ο κώδικας της εφαρμογής.
### **Function Sandbox**
Μέσα στο linux sandbox ο πηγαίος κώδικας βρίσκεται στο **`/home/site/wwwroot`** στο αρχείο **`function_app.py`** (αν χρησιμοποιείται python) ο χρήστης που εκτελεί τον κώδικα είναι **`app`** (χωρίς δικαιώματα sudo).
Μέσα στο linux sandbox, ο πηγαίος κώδικας βρίσκεται στο **`/home/site/wwwroot`** στο αρχείο **`function_app.py`** (αν χρησιμοποιείται python) ο χρήστης που εκτελεί τον κώδικα είναι **`app`** (χωρίς δικαιώματα sudo).
Σε μια **Windows** function που χρησιμοποιεί NodeJS ο κώδικας βρισκόταν στο **`C:\home\site\wwwroot\HttpTrigger1\index.js`**, το όνομα χρήστη ήταν **`mawsFnPlaceholder8_f_v4_node_20_x86`** και ήταν μέρος των **groups**: `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`.
Σε μια **Windows** function που χρησιμοποιεί NodeJS, ο κώδικας βρισκόταν στο **`C:\home\site\wwwroot\HttpTrigger1\index.js`**, το όνομα χρήστη ήταν **`mawsFnPlaceholder8_f_v4_node_20_x86`** και ήταν μέρος των **ομάδων**: `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`.
### **Managed Identities & Metadata**
Ακριβώς όπως [**VMs**](vms/index.html), οι Functions μπορούν να έχουν **Managed Identities** 2 τύπων: System assigned και User assigned.
Ακριβώς όπως [**VMs**](vms/index.html), οι Functions μπορούν να έχουν **Managed Identities** 2 τύπων: Σύστημα ανατεθειμένο και Χρήστη ανατεθειμένο.
Η **system assigned** θα είναι μια διαχειριζόμενη ταυτότητα που **μόνο η λειτουργία** που έχει ανατεθεί θα μπορεί να χρησιμοποιήσει, ενώ οι **user assigned** διαχειριζόμενες ταυτότητες είναι διαχειριζόμενες ταυτότητες που **οποιαδήποτε άλλη υπηρεσία Azure θα μπορεί να χρησιμοποιήσει**.
Η **σύστημα ανατεθειμένη** θα είναι μια διαχειριζόμενη ταυτότητα που **μόνο η λειτουργία** που έχει ανατεθεί θα μπορεί να χρησιμοποιήσει, ενώ οι **χρήστη ανατεθειμένες** διαχειριζόμενες ταυτότητες είναι διαχειριζόμενες ταυτότητες που **οποιαδήποτε άλλη υπηρεσία Azure θα μπορεί να χρησιμοποιήσει**.
> [!NOTE]
> Ακριβώς όπως στις [**VMs**](vms/index.html), οι Functions μπορούν να έχουν **1 system assigned** διαχειριζόμενη ταυτότητα και **πολλές user assigned**, επομένως είναι πάντα σημαντικό να προσπαθείτε να βρείτε όλες αυτές αν συμβιβάσετε τη λειτουργία γιατί μπορεί να μπορείτε να κλιμακώσετε προνόμια σε πολλές διαχειριζόμενες ταυτότητες από μόνο μία Function.
> Ακριβώς όπως στις [**VMs**](vms/index.html), οι Functions μπορούν να έχουν **1 σύστημα ανατεθειμένη** διαχειριζόμενη ταυτότητα και **πολλές χρήστη ανατεθειμένες**, οπότε είναι πάντα σημαντικό να προσπαθείτε να βρείτε όλες αυτές αν συμβιβάσετε τη λειτουργία, γιατί μπορεί να μπορείτε να κλιμακώσετε τα δικαιώματα σε πολλές διαχειριζόμενες ταυτότητες από μόνο μία Function.
>
> Αν δεν χρησιμοποιείται καμία διαχειριζόμενη ταυτότητα συστήματος αλλά μία ή περισσότερες διαχειριζόμενες ταυτότητες χρηστών είναι συνδεδεμένες σε μια λειτουργία, από προεπιλογή δεν θα μπορείτε να αποκτήσετε κανένα token.
> Αν δεν χρησιμοποιείται καμία διαχειριζόμενη ταυτότητα συστήματος αλλά μία ή περισσότερες διαχειριζόμενες ταυτότητες χρήστη είναι συνδεδεμένες σε μια λειτουργία, από προεπιλογή δεν θα μπορείτε να αποκτήσετε κανένα token.
Είναι δυνατό να χρησιμοποιήσετε τα [**PEASS scripts**](https://github.com/peass-ng/PEASS-ng) για να αποκτήσετε tokens από την προεπιλεγμένη διαχειριζόμενη ταυτότητα από το metadata endpoint. Ή μπορείτε να τα αποκτήσετε **χειροκίνητα** όπως εξηγείται σε:
Είναι δυνατό να χρησιμοποιήσετε τα [**PEASS scripts**](https://github.com/peass-ng/PEASS-ng) για να αποκτήσετε tokens από την προεπιλεγμένη διαχειριζόμενη ταυτότητα από το endpoint μεταδεδομένων. Ή μπορείτε να τα αποκτήσετε **χειροκίνητα** όπως εξηγείται σε:
{% 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}}
Σημειώστε ότι πρέπει να βρείτε έναν τρόπο να **ελέγξετε όλες τις Managed Identities που έχει συνδεδεμένες μια λειτουργία** καθώς αν δεν το υποδείξετε, το metadata endpoint θα **χρησιμοποιεί μόνο την προεπιλεγμένη** (ελέγξτε τον προηγούμενο σύνδεσμο για περισσότερες πληροφορίες).
Σημειώστε ότι πρέπει να βρείτε έναν τρόπο να **ελέγξετε όλες τις Managed Identities που έχει συνδεδεμένες μια λειτουργία**, καθώς αν δεν το υποδείξετε, το endpoint μεταδεδομένων θα **χρησιμοποιεί μόνο την προεπιλεγμένη** (ελέγξτε τον προηγούμενο σύνδεσμο για περισσότερες πληροφορίες).
## Access Keys
> [!NOTE]
> Σημειώστε ότι δεν υπάρχουν δικαιώματα RBAC για να δώσετε πρόσβαση σε χρήστες να καλέσουν τις λειτουργίες. Η **κλήση λειτουργίας εξαρτάται από τον ενεργοποιητή** που επιλέχθηκε κατά τη δημιουργία και αν επιλέχθηκε ένας HTTP Trigger, μπορεί να χρειαστεί να χρησιμοποιήσετε ένα **access key**.
> Σημειώστε ότι δεν υπάρχουν δικαιώματα RBAC για να δώσετε πρόσβαση σε χρήστες για να καλέσουν τις λειτουργίες. Η **κλήση της λειτουργίας εξαρτάται από τον ενεργοποιητή** που επιλέχθηκε κατά τη δημιουργία της και αν επιλέχθηκε ένας HTTP Trigger, μπορεί να χρειαστεί να χρησιμοποιήσετε ένα **access key**.
Όταν δημιουργείτε ένα endpoint μέσα σε μια λειτουργία χρησιμοποιώντας έναν **HTTP trigger** είναι δυνατό να υποδείξετε το **access key authorization level** που απαιτείται για να ενεργοποιήσετε τη λειτουργία. Διατίθενται τρεις επιλογές:
Όταν δημιουργείτε ένα endpoint μέσα σε μια λειτουργία χρησιμοποιώντας έναν **HTTP trigger**, είναι δυνατό να υποδείξετε το **επίπεδο εξουσιοδότησης access key** που απαιτείται για να ενεργοποιήσετε τη λειτουργία. Διατίθενται τρεις επιλογές:
- **ANONYMOUS**: **Όλοι** μπορούν να έχουν πρόσβαση στη λειτουργία μέσω του URL.
- **FUNCTION**: Το endpoint είναι προσβάσιμο μόνο σε χρήστες που χρησιμοποιούν ένα **function, host ή master key**.
@@ -84,10 +86,10 @@
**Τύποι κλειδιών:**
- **Function Keys:** Τα Function keys μπορεί να είναι είτε προεπιλεγμένα είτε καθορισμένα από τον χρήστη και έχουν σχεδιαστεί για να παρέχουν πρόσβαση αποκλειστικά σε **συγκεκριμένα function endpoints** εντός μιας Function App επιτρέποντας μια πιο λεπτομερή πρόσβαση στα endpoints.
- **Host Keys:** Τα Host keys, τα οποία μπορεί επίσης να είναι προεπιλεγμένα ή καθορισμένα από τον χρήστη, παρέχουν πρόσβαση σε **όλα τα function endpoints εντός μιας Function App με επίπεδο πρόσβασης FUNCTION**.
- **Master Key:** Το master key (`_master`) χρησιμεύει ως διοικητικό κλειδί που προσφέρει αυξημένα δικαιώματα, συμπεριλαμβανομένης της πρόσβασης σε όλα τα function endpoints (συμπεριλαμβανομένου του επιπέδου πρόσβασης ADMIN). Αυτό το **κλειδί δεν μπορεί να ανακληθεί.**
- **System Keys:** Τα System keys είναι **διαχειριζόμενα από συγκεκριμένες επεκτάσεις** και απαιτούνται για την πρόσβαση σε webhook endpoints που χρησιμοποιούνται από εσωτερικά στοιχεία. Παραδείγματα περιλαμβάνουν τον Event Grid trigger και τις Durable Functions, οι οποίες χρησιμοποιούν system keys για να αλληλεπιδρούν με τις αντίστοιχες APIs τους με ασφάλεια.
- **Function Keys:** Τα κλειδιά λειτουργίας μπορεί να είναι είτε προεπιλεγμένα είτε καθορισμένα από τον χρήστη και έχουν σχεδιαστεί για να παρέχουν πρόσβαση αποκλειστικά σε **συγκεκριμένα endpoints λειτουργίας** μέσα σε μια Function App, επιτρέποντας μια πιο λεπτομερή πρόσβαση στα endpoints.
- **Host Keys:** Τα κλειδιά host, τα οποία μπορεί επίσης να είναι προεπιλεγμένα ή καθορισμένα από τον χρήστη, παρέχουν πρόσβαση σε **όλα τα endpoints λειτουργίας μέσα σε μια Function App με επίπεδο πρόσβασης FUNCTION**.
- **Master Key:** Το master key (`_master`) χρησιμεύει ως διοικητικό κλειδί που προσφέρει ανυψωμένα δικαιώματα, συμπεριλαμβανομένης της πρόσβασης σε όλα τα endpoints λειτουργίας (συμπεριλαμβανομένου του επιπέδου πρόσβασης ADMIN). Αυτό το **κλειδί δεν μπορεί να ανακληθεί.**
- **System Keys:** Τα συστήματα κλειδιά είναι **διαχειριζόμενα από συγκεκριμένες επεκτάσεις** και απαιτούνται για την πρόσβαση σε webhook endpoints που χρησιμοποιούνται από εσωτερικά στοιχεία. Παραδείγματα περιλαμβάνουν τον ενεργοποιητή Event Grid και τις Durable Functions, οι οποίες χρησιμοποιούν συστήματα κλειδιά για να αλληλεπιδρούν με τις αντίστοιχες APIs τους με ασφάλεια.
> [!TIP]
> Παράδειγμα για την πρόσβαση σε ένα endpoint API λειτουργίας χρησιμοποιώντας ένα κλειδί:
@@ -104,7 +106,7 @@ az-app-services.md
### Github Based Deployments
Όταν μια λειτουργία δημιουργείται από ένα Github repo, η διαδικτυακή κονσόλα Azure επιτρέπει να **δημιουργηθεί αυτόματα ένα Github Workflow σε ένα συγκεκριμένο αποθετήριο** έτσι ώστε κάθε φορά που αυτό το αποθετήριο ενημερώνεται, ο κώδικας της λειτουργίας να ενημερώνεται. Στην πραγματικότητα, το Github Action yaml για μια python function φαίνεται έτσι:
Όταν μια λειτουργία δημιουργείται από ένα αποθετήριο Github, η διαδικτυακή κονσόλα Azure επιτρέπει να **δημιουργηθεί αυτόματα μια Github Workflow σε ένα συγκεκριμένο αποθετήριο** έτσι ώστε όποτε αυτό το αποθετήριο ενημερώνεται, ο κώδικας της λειτουργίας να ενημερώνεται. Στην πραγματικότητα, το YAML του Github Action για μια python function φαίνεται έτσι:
<details>
@@ -192,18 +194,18 @@ package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
```
</details>
Επιπλέον, μια **Διαχειριζόμενη Ταυτότητα** δημιουργείται ώστε η Github Action από το αποθετήριο να μπορεί να συνδεθεί στο Azure με αυτή. Αυτό γίνεται με τη δημιουργία ενός Ομοσπονδιακού διαπιστευτηρίου πάνω στη **Διαχειριζόμενη Ταυτότητα** επιτρέποντας στον **Εκδότη** `https://token.actions.githubusercontent.com` και τον **Αναγνωριστή Υποκειμένου** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>`.
Επιπλέον, μια **Διαχειριζόμενη Ταυτότητα** δημιουργείται ώστε η Github Action από το αποθετήριο να μπορεί να συνδεθεί στο Azure με αυτή. Αυτό γίνεται με τη δημιουργία ενός Ομοσπονδιακού διαπιστευτηρίου πάνω στη **Διαχειριζόμενη Ταυτότητα** επιτρέποντας στον **Εκδότη** `https://token.actions.githubusercontent.com` και τον **Αναγνωριστικό Υποκειμένου** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>`.
> [!CAUTION]
> Επομένως, οποιοσδήποτε συμβιβάσει αυτό το αποθετήριο θα μπορεί να συμβιβάσει τη λειτουργία και τις Διαχειριζόμενες Ταυτότητες που είναι συνδεδεμένες με αυτή.
> Επομένως, οποιοσδήποτε παραβιάσει αυτό το αποθετήριο θα μπορεί να παραβιάσει τη λειτουργία και τις Διαχειριζόμενες Ταυτότητες που είναι συνδεδεμένες με αυτή.
### Αναπτύξεις Βασισμένες σε Κοντέινερ
Όλα τα σχέδια δεν επιτρέπουν την ανάπτυξη κοντέινερ, αλλά για εκείνα που το επιτρέπουν, η διαμόρφωση θα περιέχει τη διεύθυνση URL του κοντέινερ. Στην API, η ρύθμιση **`linuxFxVersion`** θα έχει κάτι σαν: `DOCKER|mcr.microsoft.com/...`, ενώ στην ιστοσελίδα κονσόλας, η διαμόρφωση θα δείχνει τις **ρυθμίσεις εικόνας**.
Όλα τα σχέδια δεν επιτρέπουν την ανάπτυξη κοντέινερ, αλλά για εκείνα που το επιτρέπουν, η διαμόρφωση θα περιέχει τη διεύθυνση URL του κοντέινερ. Στην API, η ρύθμιση **`linuxFxVersion`** θα έχει κάτι σαν: `DOCKER|mcr.microsoft.com/...`, ενώ στην κονσόλα ιστού, η διαμόρφωση θα δείχνει τις **ρυθμίσεις εικόνας**.
Επιπλέον, **κανένας πηγαίος κώδικας δεν θα αποθηκευτεί στον λογαριασμό αποθήκευσης** που σχετίζεται με τη λειτουργία καθώς δεν είναι απαραίτητος.
## Αριθμητική
## Αρίθμηση
{{#tabs }}
{{#tab name="az cli" }}
@@ -211,10 +213,10 @@ package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
# 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);
};
})();