mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-03-12 21:22:57 -07:00
Translated ['src/pentesting-cloud/azure-security/az-privilege-escalation
This commit is contained in:
@@ -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
BIN
src/images/lasttower.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -4,7 +4,7 @@
|
||||
|
||||
## App Services
|
||||
|
||||
For more information about Azure App services check:
|
||||
Azure App hizmetleri hakkında daha fazla bilgi için kontrol edin:
|
||||
|
||||
{{#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**:
|
||||
Bu izinler, bir web uygulaması içinde **SSH shell** almayı sağlar. Ayrıca uygulamayı **debug** etmeye de izin verir.
|
||||
|
||||
- **Tek komutla SSH**:
|
||||
```bash
|
||||
# Direct option
|
||||
az webapp ssh --name <name> --resource-group <res-group>
|
||||
```
|
||||
|
||||
- **Create tunnel and then connect to SSH**:
|
||||
|
||||
- **Tünel oluşturun ve ardından SSH'ye bağlanın**:
|
||||
```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
|
||||
```
|
||||
- **Uygulamayı Hata Ayıklama**:
|
||||
1. VScode'da Azure uzantısını yükleyin.
|
||||
2. Uzantıda Azure hesabınızla oturum açın.
|
||||
3. Abonelik içindeki tüm Uygulama hizmetlerini listeleyin.
|
||||
4. Hata ayıklamak istediğiniz Uygulama hizmetini seçin, sağ tıklayın ve "Hata Ayıklamayı Başlat"ı seçin.
|
||||
5. Uygulama hata ayıklama etkin değilse, uzantı bunu etkinleştirmeye çalışacaktır ancak hesabınızın bunu yapabilmesi için `Microsoft.Web/sites/config/write` iznine ihtiyacı vardır.
|
||||
|
||||
- **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 Kimlik Bilgilerini Alma ve Temel Kimlik Doğrulamayı Etkinleştirme
|
||||
|
||||
### 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 kimlik bilgilerini almak için aşağıdaki **komutları ve izinleri** kullanabilirsiniz:
|
||||
|
||||
- **`Microsoft.Web/sites/publishxml/action`** izni çağrılmasına izin verir:
|
||||
```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"
|
||||
}
|
||||
]
|
||||
```
|
||||
Kullanıcı adının **her zaman aynı olduğunu** (FTP'de uygulamanın adının başında eklendiği hariç) ve **şifrenin hepsi için aynı olduğunu** not edin.
|
||||
|
||||
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:
|
||||
Ayrıca, **SCM URL'si `<app-name>.scm.azurewebsites.net`**'dir.
|
||||
|
||||
- İzin **`Microsoft.Web/sites/config/list/action`** çağrılmasına izin verir:
|
||||
```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"
|
||||
}
|
||||
```
|
||||
Not edin ki **kimlik bilgileri aynı**dır önceki komutta.
|
||||
|
||||
Note how the **credentials are the same** as in the previous command.
|
||||
|
||||
- Another option would be to **set you own creds** and use them:
|
||||
|
||||
- Başka bir seçenek, **kendi kimlik bilgilerinizi ayarlamak** ve bunları kullanmaktır:
|
||||
```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
|
||||
```
|
||||
Sonra, bu kimlik bilgilerini **SCM ve FTP platformlarına erişmek için** kullanabilirsiniz. Bu, kalıcılığı sağlamak için de harika bir yoldur.
|
||||
|
||||
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 platformuna **web üzerinden erişmek için `<SCM-URL>/BasicAuth`** adresine gitmeniz gerektiğini unutmayın.
|
||||
|
||||
> [!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`):
|
||||
> Her kullanıcının önceki komutu çağırarak kendi kimlik bilgilerini yapılandırabileceğini unutmayın, ancak kullanıcı SCM veya FTP'ye erişim için yeterli izne sahip değilse, kimlik bilgileri çalışmayacaktır.
|
||||
|
||||
- Bu kimlik bilgilerini **REDACTED** olarak görüyorsanız, bunun nedeni **SCM temel kimlik doğrulama seçeneğini etkinleştirmeniz gerektiğidir** ve bunun için ikinci izne (`Microsoft.Web/sites/basicPublishingCredentialsPolicies/write`) ihtiyacınız var:
|
||||
```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 kimlik bilgilerini kullanarak kod yayınlama
|
||||
|
||||
### Publish code using SCM credentials
|
||||
Geçerli SCM kimlik bilgilerine sahip olmak, **kod yayınlamak** için mümkündür. Bu, aşağıdaki komut kullanılarak yapılabilir.
|
||||
|
||||
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:
|
||||
Bu python örneği için https://github.com/Azure-Samples/msdocs-python-flask-webapp-quickstart adresinden repoyu indirebilir, istediğiniz **değişiklikleri** yapabilir ve ardından **zip'leyebilirsiniz: `zip -r app.zip .`**.
|
||||
|
||||
Ardından, aşağıdaki komut ile bir web uygulamasında **kod yayınlayabilirsiniz**:
|
||||
```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 kimlik bilgileri
|
||||
|
||||
### 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:
|
||||
Bahsedilen Azure izni, SCM kimlik bilgileriyle de gerçekleştirilebilecek birkaç ilginç eylemi gerçekleştirmeye olanak tanır:
|
||||
|
||||
- **Webjobs** günlüklerini oku:
|
||||
```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** kaynak kodunu okuyun:
|
||||
```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**:
|
||||
|
||||
- **Sürekli Webjob** oluşturun:
|
||||
```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.
|
||||
|
||||
Bu izinler, **yönetilen bir kimlik atamaya** olanak tanır, bu nedenle bir App hizmeti daha önce tehlikeye atıldıysa, bu saldırganın App hizmetine yeni yönetilen kimlikler atamasına ve **yetkileri artırmasına** olanak tanır.
|
||||
```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.
|
||||
|
||||
Bu izin, veritabanı kimlik bilgileri gibi hassas bilgileri içerebilecek App hizmetinin **bağlantı dizelerini** ve **uygulama ayarlarını** listelemeye olanak tanır.
|
||||
```bash
|
||||
az webapp config connection-string list --name <name> --resource-group <res-group>
|
||||
az webapp config appsettings list --name <name> --resource-group <res-group>
|
||||
```
|
||||
### Yapılandırılmış Üçüncü Taraf Kimlik Bilgilerini Okuma
|
||||
|
||||
### 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.
|
||||
|
||||
Aşağıdaki komutu çalıştırarak **üçüncü taraf kimlik bilgilerini** mevcut hesapta okumak mümkündür. Örneğin, bazı Github kimlik bilgileri farklı bir kullanıcıda yapılandırılmışsa, farklı bir kullanıcıdan token'a erişemeyeceğinizi unutmayın.
|
||||
```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"
|
||||
```
|
||||
Bu komut, Github, Bitbucket, Dropbox ve OneDrive için token'lar döndürür.
|
||||
|
||||
This command returns tokens for Github, Bitbucket, Dropbox and OneDrive.
|
||||
|
||||
Here you have some command examples to check the tokens:
|
||||
|
||||
İşte token'ları kontrol etmek için bazı komut örnekleri:
|
||||
```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
|
||||
```
|
||||
### Uygulama Kodunu Kaynaktan Güncelle
|
||||
|
||||
### 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>`
|
||||
- Yapılandırılmış kaynak bir üçüncü taraf sağlayıcı (Github, BitBucket veya Azure Repository gibi) ise, kaynak kodunu tehlikeye atarak **uygulama kodunu güncelleyebilirsiniz**.
|
||||
- Uygulama **uzaktan bir git deposu** (kullanıcı adı ve şifre ile) kullanacak şekilde yapılandırılmışsa, değişiklikleri klonlamak ve itmek için **URL ve temel kimlik bilgilerini** almak mümkündür:
|
||||
- İzin kullanarak **`Microsoft.Web/sites/sourcecontrols/read`**: `az webapp deployment source show --name <app-name> --resource-group <res-group>`
|
||||
- İzin kullanarak **`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"`
|
||||
- Uygulama **yerel bir git deposu** kullanacak şekilde yapılandırılmışsa, **depolamayı klonlamak** ve **değişiklikleri itmek** mümkündür:
|
||||
- İzin kullanarak **`Microsoft.Web/sites/sourcecontrols/read`**: `az webapp deployment source show --name <app-name> --resource-group <res-group>` ile git deposunun URL'sini alabilirsiniz, ancak bu, uygulamanın SCM URL'si ile aynı olacak ve `/<app-name>.git` yolunu içerecektir (örneğin, `https://pythonwebapp-audeh9f5fzeyhhed.scm.canadacentral-01.azurewebsites.net:443/pythonwebapp.git`).
|
||||
- SCM kimliğini almak için aşağıdaki izne ihtiyacınız var:
|
||||
- **`Microsoft.Web/sites/publishxml/action`**: Ardından `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>` komutunu çalıştırın.
|
||||
- **`Microsoft.Web/sites/config/list/action`**: Ardından `az webapp deployment list-publishing-credentials --name <name> --resource-group <res-group>` komutunu çalıştırın.
|
||||
|
||||
> [!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` iznine ve SCM kimlik bilgilerine sahip olmanın, daha önceki bir bölümde belirtildiği gibi, bir web uygulamasına dağıtım yapmanın her zaman mümkün olduğunu unutmayın (üçüncü taraf bir sağlayıcı kullanacak şekilde yapılandırılmış olsa bile).
|
||||
|
||||
> [!WARNING]
|
||||
> Note that having the permissions below it's also **possible to execute an arbitrary container** even if the webapp was configured differently.
|
||||
> Aşağıdaki izinlere sahip olmanın, web uygulaması farklı bir şekilde yapılandırılmış olsa bile **rastgele bir konteyneri çalıştırmanın** de mümkün olduğunu unutmayın.
|
||||
|
||||
### `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.
|
||||
|
||||
Bu, bir web uygulaması tarafından kullanılan **konteyneri değiştirmeye** olanak tanıyan izinler setidir. Bir saldırgan bunu kötü niyetli bir konteyner çalıştırmak için kötüye kullanabilir.
|
||||
```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}}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
## App Service Temel Bilgiler
|
||||
## App Service Temel Bilgileri
|
||||
|
||||
Azure App Services, geliştiricilerin **web uygulamaları, mobil uygulama arka uçları ve API'leri sorunsuz bir şekilde oluşturmasına, dağıtmasına ve ölçeklendirmesine** olanak tanır. Birden fazla programlama dilini destekler ve geliştirilmiş işlevsellik ve yönetim için çeşitli Azure araçları ve hizmetleri ile entegre olur.
|
||||
Azure App Services, geliştiricilerin **web uygulamaları, mobil uygulama arka uçları ve API'leri sorunsuz bir şekilde oluşturmasını, dağıtmasını ve ölçeklendirmesini** sağlar. Birden fazla programlama dilini destekler ve geliştirilmiş işlevsellik ve yönetim için çeşitli Azure araçları ve hizmetleri ile entegre olur.
|
||||
|
||||
Her uygulama bir kumanda alanında çalışır, ancak izolasyon App Service planlarına bağlıdır:
|
||||
|
||||
@@ -13,26 +13,27 @@ Her uygulama bir kumanda alanında çalışır, ancak izolasyon App Service plan
|
||||
- İzole katmanlar, uygulamaların izolasyonunu artırarak **özel sanal ağlarda özel VM'lerde** çalışır.
|
||||
|
||||
> [!WARNING]
|
||||
> Bu izolasyonların **hiçbiri** diğer yaygın **web güvenlik açıklarını** (örneğin dosya yükleme veya enjeksiyonlar) **önlemez**. Ve bir **yönetim kimliği** kullanılıyorsa, bu kimliklerin **yetkileri artırmasına** olanak tanıyabilir.
|
||||
> Bu izolasyonların **hiçbiri** diğer yaygın **web güvenlik açıklarını** (örneğin dosya yükleme veya enjeksiyonlar) **önlemez**. Ve bir **yönetim kimliği** kullanılıyorsa, bu kimlik **yetkileri artırabilir**.
|
||||
|
||||
Uygulamaların bazı ilginç yapılandırmaları vardır:
|
||||
|
||||
- **Her Zaman Açık**: Uygulamanın her zaman çalışmasını sağlar. Eğer etkinleştirilmezse, uygulama 20 dakikalık bir etkinlik yokluğundan sonra duracak ve bir istek alındığında tekrar başlayacaktır.
|
||||
- Eğer sürekli çalışması gereken bir webjob'unuz varsa bu çok önemlidir, çünkü uygulama durursa webjob da duracaktır.
|
||||
- **Her Zaman Açık**: Uygulamanın her zaman çalışmasını sağlar. Eğer etkinleştirilmezse, uygulama 20 dakika boyunca etkinlik olmadığında duracak ve bir istek alındığında tekrar başlayacaktır.
|
||||
- Eğer sürekli çalışması gereken bir webjob'unuz varsa bu önemlidir, çünkü uygulama durursa webjob da duracaktır.
|
||||
- **SSH**: Etkinleştirildiğinde, yeterli izinlere sahip bir kullanıcı uygulamaya SSH ile bağlanabilir.
|
||||
- **Hata Ayıklama**: Etkinleştirildiğinde, yeterli izinlere sahip bir kullanıcı uygulamayı hata ayıklayabilir. Ancak, bu her 48 saatte bir otomatik olarak devre dışı bırakılır.
|
||||
- **Web Uygulaması + Veritabanı**: Web konsolu, bir veritabanı ile bir Uygulama oluşturmayı sağlar. Bu durumda kullanılacak veritabanını (SQLAzure, PostgreSQL, MySQL, MongoDB) seçmek mümkündür ve ayrıca bir Azure Cache for Redis oluşturmanıza da olanak tanır.
|
||||
- **Web Uygulaması + Veritabanı**: Web konsolu, bir veritabanı ile bir Uygulama oluşturmayı sağlar. Bu durumda kullanılacak veritabanını (SQLAzure, PostgreSQL, MySQL, MongoDB) seçmek mümkündür ve ayrıca bir Azure Cache for Redis oluşturmanıza da izin verir.
|
||||
- Veritabanı ve Redis için kimlik bilgilerini içeren URL, **appsettings** içinde saklanacaktır.
|
||||
- **Konteyner**: Konteynerin URL'sini ve erişim kimlik bilgilerini belirterek App Service'e bir konteyner dağıtmak mümkündür.
|
||||
- **Mounts**: Azure Blob (Sadece Okuma) veya Azure Files olan Depolama hesaplarından 5 mount oluşturmak mümkündür. Yapılandırma, Depolama Hesabı üzerindeki erişim anahtarını saklayacaktır.
|
||||
- **Mounts**: Azure Blob (Salt Okunur) veya Azure Files olan Depolama hesaplarından 5 mount oluşturmak mümkündür. Yapılandırma, Depolama Hesabı üzerindeki erişim anahtarını saklayacaktır.
|
||||
- **Ağ**: Genel olarak erişilebilir veya yalnızca bir VNet'ten özel uç noktalarla erişilebilir olabilir.
|
||||
|
||||
## Temel Kimlik Doğrulama
|
||||
|
||||
Bir web uygulaması (ve genellikle bir Azure işlevi) oluştururken, **Temel Kimlik Doğrulama'nın etkinleştirilip etkinleştirilmeyeceğini** belirtmek mümkündür (varsayılan olarak devre dışıdır). Bu, uygulama için **SCM (Kaynak Kontrol Yöneticisi) ve FTP (Dosya Transfer Protokolü)**'nü etkinleştirir, böylece uygulamayı bu teknolojileri kullanarak dağıtmak mümkün olacaktır.
|
||||
Bir web uygulaması (ve genellikle bir Azure işlevi) oluştururken, **Temel Kimlik Doğrulama'nın etkinleştirilip etkinleştirilmeyeceğini** belirtmek mümkündür (varsayılan olarak devre dışı). Bu, uygulama için **SCM (Kaynak Kontrol Yöneticisi) ve FTP (Dosya Transfer Protokolü)**'nü etkinleştirir, böylece bu teknolojileri kullanarak uygulamayı dağıtmak mümkün olacaktır.
|
||||
|
||||
SCM ve FTP sunucularına erişmek için bir **kullanıcı adı ve şifre** gereklidir. Bu nedenle, Azure bu platformlara ve kimlik bilgilerine erişim sağlamak için bazı **API'ler** sunar.
|
||||
SCM ve FTP sunucularına erişmek için bir **kullanıcı adı ve şifre** gereklidir. Bu nedenle, Azure bu platformlara ve kimlik bilgilerine erişmek için bazı **API'ler sağlar**.
|
||||
|
||||
**FTP sunucusunda herhangi bir özel sihir yoktur**, geçerli bir URL, kullanıcı adı ve şifre ile bağlanmak ve App ortamında okuma ve yazma izinleri almak mümkündür.
|
||||
**FTP sunucusu özel bir sihir** içermez, geçerli URL, kullanıcı adı ve şifre ile bağlanmak ve Uygulama ortamında okuma ve yazma izinleri almak mümkündür.
|
||||
|
||||
SCM
|
||||
SCM'ye bir web tarayıcısı kullanarak `https://<SMC-URL>/BasicAuth` adresinden bağlanabilir ve oradaki tüm dosyaları ve dağıtımları kontrol edebilirsiniz.
|
||||
@@ -52,25 +53,25 @@ Kudu'da bulabileceğiniz bazı ilginç uç noktalar:
|
||||
- `/Env`: Sistem, uygulama ayarları, çevre değişkenleri, bağlantı dizeleri ve HTTP başlıkları hakkında bilgi alır.
|
||||
- `/wwwroot/`: Web uygulamasının kök dizini. Buradan tüm dosyaları indirebilirsiniz.
|
||||
|
||||
Ayrıca, Kudu daha önce [https://github.com/projectkudu/kudu](https://github.com/projectkudu/kudu) adresinde açık kaynaklıydı, ancak proje kullanımdan kaldırıldı ve Azure'daki mevcut Kudu ile eski Kudu'nun davranışını karşılaştırdığınızda **birçok şeyin değiştiğini** görebilirsiniz.
|
||||
Ayrıca, Kudu daha önce [https://github.com/projectkudu/kudu](https://github.com/projectkudu/kudu) adresinde açık kaynaklıydı, ancak proje kullanımdan kaldırıldı ve Azure'daki mevcut Kudu'nun eski ile karşılaştırıldığında **birçok şeyin değiştiği** görülebilir.
|
||||
|
||||
## Kaynaklar
|
||||
|
||||
App Services, varsayılan olarak kodu bir zip dosyası olarak yüklemeye izin verir, ancak aynı zamanda bir üçüncü taraf hizmetine bağlanarak kodu oradan almanıza da olanak tanır.
|
||||
App Services, varsayılan olarak kodu bir zip dosyası olarak yüklemeye izin verir, ancak ayrıca bir üçüncü taraf hizmetine bağlanarak kodu oradan almayı da sağlar.
|
||||
|
||||
- Şu anda desteklenen üçüncü taraf kaynaklar **Github** ve **Bitbucket**'dır.
|
||||
- Kimlik doğrulama tokenlerini almak için `az rest --url "https://management.azure.com/providers/Microsoft.Web/sourcecontrols?api-version=2024-04-01"` komutunu çalıştırabilirsiniz.
|
||||
- Azure varsayılan olarak, kod güncellendiğinde her seferinde App Service'e dağıtmak için bir **Github Action** ayarlayacaktır.
|
||||
- Ayrıca, oradan kod almak için bir **uzaktan git deposu** (kullanıcı adı ve şifre ile) belirtmek de mümkündür.
|
||||
- Uzaktan depo için kimlik bilgilerini almak için `az webapp deployment source show --name <app-name> --resource-group <res-group>` veya `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"` komutunu çalıştırabilirsiniz.
|
||||
- Ayrıca bir **Azure Repository** kullanmak da mümkündür.
|
||||
- **Yerel bir git deposu** yapılandırmak da mümkündür.
|
||||
- Ayrıca, bir **Azure Repository** kullanmak da mümkündür.
|
||||
- Bir **yerel git deposu** yapılandırmak da mümkündür.
|
||||
- Git deposunun URL'sini `az webapp deployment source show --name <app-name> --resource-group <res-group>` komutunu çalıştırarak alabilirsiniz ve bu, uygulamanın SCM URL'si olacaktır.
|
||||
- Klonlamak için, `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>` komutuyla alabileceğiniz SCM kimlik bilgilerine ihtiyacınız olacaktır.
|
||||
- Klonlamak için `az webapp deployment list-publishing-profiles --resource-group <res-group> -n <name>` komutuyla alabileceğiniz SCM kimlik bilgilerine ihtiyacınız olacaktır.
|
||||
|
||||
## Webjobs
|
||||
|
||||
Azure WebJobs, **Azure App Service ortamında çalışan arka plan görevleridir**. Geliştiricilerin, web uygulamalarıyla birlikte betikler veya programlar çalıştırmasına olanak tanır ve dosya işleme, veri işleme veya zamanlanmış görevler gibi asenkron veya zaman alıcı işlemleri daha kolay yönetmelerini sağlar.
|
||||
Azure WebJobs, **Azure App Service ortamında çalışan arka plan görevleridir**. Geliştiricilerin, web uygulamalarıyla birlikte betikler veya programlar çalıştırmasına olanak tanır ve dosya işleme, veri işleme veya planlı görevler gibi zaman alıcı veya asenkron işlemleri daha kolay yönetmelerini sağlar.
|
||||
İki tür web job vardır:
|
||||
- **Sürekli**: Sonsuz bir döngüde çalışır ve oluşturulur oluşturulmaz tetiklenir. Sürekli işleme gerektiren görevler için idealdir. Ancak, uygulama çalışmayı durdurursa ve son 20 dakikada bir istek almazsa, web job da duracaktır.
|
||||
- **Tetiklenmiş**: Talep üzerine veya bir programa göre çalışır. Periyodik görevler, örneğin toplu veri güncellemeleri veya bakım rutinleri için en uygun olanıdır.
|
||||
@@ -81,13 +82,13 @@ Ayrıca, Webjobs tarafından üretilen **logları** kontrol etmek her zaman ilgi
|
||||
|
||||
## Slots
|
||||
|
||||
Azure App Service Slots, **uygulamanın farklı sürümlerini** aynı App Service'e dağıtmak için kullanılır. Bu, geliştiricilerin yeni özellikleri veya değişiklikleri üretim ortamına dağıtmadan önce ayrı bir ortamda test etmelerine olanak tanır.
|
||||
Azure App Service Slots, **uygulamanın farklı sürümlerini** aynı App Service'e dağıtmak için kullanılır. Bu, geliştiricilerin yeni özellikleri veya değişiklikleri üretim ortamına dağıtmadan önce ayrı bir ortamda test etmelerini sağlar.
|
||||
|
||||
Ayrıca, belirli bir slota **trafik yüzdesi** yönlendirmek mümkündür, bu da A/B testleri ve **arka kapı amaçları** için faydalıdır.
|
||||
|
||||
## Azure Function Apps
|
||||
|
||||
Temelde **Azure Function uygulamaları, Azure App Service'in** bir alt kümesidir ve web konsoluna gittiğinizde tüm uygulama hizmetlerini listelediğinizde veya az cli'de `az webapp list` komutunu çalıştırdığınızda **Function uygulamalarının da orada listelendiğini göreceksiniz**.
|
||||
Temelde **Azure Function uygulamaları, Azure App Service'in** bir alt kümesidir ve web konsolunda, web konsoluna gidip tüm uygulama hizmetlerini listelediğinizde veya az cli'de `az webapp list` komutunu çalıştırdığınızda **Function uygulamalarının da orada listelendiğini göreceksiniz**.
|
||||
|
||||
Bu nedenle, her iki hizmet de aslında çoğunlukla **aynı yapılandırmalara, özelliklere ve az cli'deki seçeneklere** sahiptir, ancak bunları biraz farklı yapılandırabilirler (örneğin appsettings'in varsayılan değerleri veya Function uygulamalarında bir Depolama Hesabı kullanımı).
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -292,7 +297,7 @@ SCM portalına giriş yaparak veya FTP üzerinden bağlanarak `/wwwroot` dizinin
|
||||
|
||||
### Github'dan Python
|
||||
|
||||
Bu eğitim, önceki eğitimle aynı temele dayanmakta ancak bir Github deposu kullanmaktadır.
|
||||
Bu eğitim, önceki eğitime dayanmaktadır ancak bir Github deposu kullanmaktadır.
|
||||
|
||||
1. Github hesabınızda msdocs-python-flask-webapp-quickstart deposunu fork edin.
|
||||
2. Azure'da yeni bir Python Web Uygulaması oluşturun.
|
||||
|
||||
@@ -4,72 +4,74 @@
|
||||
|
||||
## Temel Bilgiler
|
||||
|
||||
**Azure Function Apps**, temel altyapıyı yönetmeden **fonksiyon** adı verilen küçük kod parçalarını çalıştırmanıza olanak tanıyan bir **sunucusuz hesaplama hizmetidir**. HTTP istekleri, zamanlayıcılar veya Blob Depolama veya Olay Hub'ları gibi diğer Azure hizmetlerinden gelen olaylar gibi çeşitli tetikleyicilere yanıt olarak kod çalıştırmak için tasarlanmıştır. Function Apps, C#, Python, JavaScript ve Java gibi birden fazla programlama dilini destekleyerek **olay odaklı uygulamalar**, iş akışlarını otomatikleştirme veya hizmetleri entegre etme için çok yönlü hale getirir. Genellikle kodunuz çalıştığında yalnızca kullanılan hesaplama süresi için ödeme yapmanız gerektiğinden maliyet açısından etkilidir.
|
||||
**Azure Function Apps**, temel altyapıyı yönetmeden **fonksiyon** adı verilen küçük kod parçalarını çalıştırmanıza olanak tanıyan bir **sunucusuz hesaplama hizmetidir**. HTTP istekleri, zamanlayıcılar veya Blob Depolama veya Event Hubs gibi diğer Azure hizmetlerinden gelen olaylar gibi çeşitli tetikleyicilere yanıt olarak kodu çalıştırmak için tasarlanmıştır. Function Apps, C#, Python, JavaScript ve Java gibi birden fazla programlama dilini destekleyerek **olay odaklı uygulamalar**, iş akışlarını otomatikleştirme veya hizmetleri entegre etme için çok yönlü hale getirir. Genellikle kodunuz çalıştığında yalnızca kullanılan hesaplama süresi için ödeme yaparsınız, bu da maliyet etkin olmalarını sağlar.
|
||||
|
||||
> [!NOTE]
|
||||
> **Fonksiyonlar, Uygulama Hizmetlerinin** bir alt kümesidir, bu nedenle burada tartışılan birçok özellik, Azure Uygulamaları (`webapp` cli'de) olarak oluşturulan uygulamalar tarafından da kullanılacaktır.
|
||||
|
||||
### Farklı Planlar
|
||||
|
||||
- **Esnek Tüketim Planı**: Talebe göre fonksiyon örneklerini ekleyip kaldırarak **dinamik, olay odaklı ölçeklendirme** sunar ve kullanım başına ödeme modeli ile çalışır. **Sanal ağ** ve **önceden sağlanmış örnekler** ile soğuk başlangıçları azaltır, bu da konteyner desteği gerektirmeyen **değişken iş yükleri** için uygun hale getirir.
|
||||
- **Geleneksel Tüketim Planı**: Fonksiyonlar çalıştığında yalnızca hesaplama kaynakları için **ödeme yaptığınız** varsayılan sunucusuz seçenektir. Gelen olaylara göre otomatik olarak ölçeklenir ve **soğuk başlangıç optimizasyonları** içerir, ancak konteyner dağıtımlarını desteklemez. Otomatik ölçeklendirme gerektiren **kesintili iş yükleri** için idealdir.
|
||||
- **Premium Plan**: **Tutarlı performans** için tasarlanmıştır, soğuk başlangıçları ortadan kaldırmak için **önceden ısıtılmış işçiler** sunar. **Uzun süreli yürütme süreleri, sanal ağ** sunar ve **özel Linux görüntülerini** destekler, bu da yüksek performans ve gelişmiş özellikler gerektiren **misyon kritik uygulamalar** için mükemmel hale getirir.
|
||||
- **Özel Plan**: **Öngörülebilir faturalama** ile özel sanal makinelerde çalışır ve manuel veya otomatik ölçeklendirmeyi destekler. Aynı planda birden fazla uygulama çalıştırmaya olanak tanır, **hesaplama izolasyonu** sağlar ve **App Service Environments** aracılığıyla **güvenli ağ erişimi** sağlar, bu da tutarlı kaynak tahsisi gerektiren **uzun süreli uygulamalar** için idealdir.
|
||||
- **Flex Tüketim Planı**: Talebe göre fonksiyon örneklerini ekleyip çıkararak **dinamik, olay odaklı ölçekleme** sunar ve kullanım başına ödeme modeli ile çalışır. **Sanal ağ** ve soğuk başlangıçları azaltmak için **önceden tahsis edilmiş örnekler** destekler, bu da konteyner desteği gerektirmeyen **değişken iş yükleri** için uygundur.
|
||||
- **Geleneksel Tüketim Planı**: Fonksiyonlar çalıştığında yalnızca hesaplama kaynakları için **ödeme yaptığınız** varsayılan sunucusuz seçenektir. Gelen olaylara göre otomatik olarak ölçeklenir ve **soğuk başlangıç optimizasyonları** içerir, ancak konteyner dağıtımlarını desteklemez. Otomatik ölçekleme gerektiren **kesintili iş yükleri** için idealdir.
|
||||
- **Premium Plan**: **Soğuk başlangıçları ortadan kaldırmak için önceden ısıtılmış işçiler** ile **tutarlı performans** sağlamak için tasarlanmıştır. **Uzun süreli yürütme süreleri, sanal ağ** sunar ve **özel Linux görüntülerini** destekler, bu da yüksek performans ve gelişmiş özellikler gerektiren **misyon kritik uygulamalar** için mükemmeldir.
|
||||
- **Özel Plan**: **Öngörülebilir faturalama** ile özel sanal makinelerde çalışır ve manuel veya otomatik ölçeklemeyi destekler. Aynı planda birden fazla uygulama çalıştırmaya olanak tanır, **hesaplama izolasyonu** sağlar ve **App Service Environments** aracılığıyla **güvenli ağ erişimi** sağlar, bu da tutarlı kaynak tahsisi gerektiren **uzun süreli uygulamalar** için idealdir.
|
||||
- **Konteyner Uygulamaları**: **Konteynerleştirilmiş fonksiyon uygulamalarını** yönetilen bir ortamda, mikro hizmetler ve API'lerle birlikte dağıtmayı sağlar. Özel kütüphaneleri, eski uygulama geçişini ve **GPU işleme** destekler, Kubernetes küme yönetimini ortadan kaldırır. **Olay odaklı, ölçeklenebilir konteynerleştirilmiş uygulamalar** için idealdir.
|
||||
|
||||
### **Depolama Kovalari**
|
||||
|
||||
Yeni bir konteynerleştirilmemiş Function App oluşturduğunuzda (ancak çalıştırılacak kodu veriyorsanız), **kod ve diğer Fonksiyon ile ilgili veriler bir Depolama hesabında saklanacaktır**. Varsayılan olarak, web konsolu her fonksiyon için kodu saklamak üzere yeni bir tane oluşturacaktır.
|
||||
|
||||
Ayrıca, kova içindeki kodu (farklı formatlarda saklanabileceği) değiştirdiğinizde, **uygulamanın kodu yeni olanla değiştirilecek ve bir sonraki fonksiyon çağrıldığında çalıştırılacaktır**.
|
||||
Ayrıca, kova içindeki kodu (farklı formatlarda saklanabileceği) değiştirdiğinizde, **uygulamanın kodu yeni olanla değiştirilecek ve bir sonraki Fonksiyon çağrıldığında çalıştırılacaktır**.
|
||||
|
||||
> [!CAUTION]
|
||||
> Bu, bir saldırganın perspektifinden çok ilginçtir çünkü **bu kovaya yazma erişimi**, bir saldırganın **kodunuzu tehlikeye atmasına ve Fonksiyon Uygulamasındaki yönetilen kimliklere yetki yükseltmesine** olanak tanıyacaktır.
|
||||
> Bu, bir saldırganın **bu kovaya yazma erişimi** ile **kodunuzu tehlikeye atmasına ve Fonksiyon Uygulamasındaki yönetilen kimliklere yetki yükseltmesine** olanak tanıdığı için saldırganlar açısından çok ilginçtir.
|
||||
>
|
||||
> Bununla ilgili daha fazla bilgi **yetki yükseltme bölümünde**.
|
||||
> Bununla ilgili daha fazla bilgi **yetki yükseltme bölümünde** bulunmaktadır.
|
||||
|
||||
Ayrıca, **`azure-webjobs-secrets`** konteynerinde depolama hesabında saklanan **master ve fonksiyon anahtarlarını** bulmak da mümkündür, bu da **`<app-name>`** klasöründe bulunan JSON dosyalarında yer almaktadır.
|
||||
Ayrıca, **`azure-webjobs-secrets`** konteynerinde depolama hesabında saklanan **master ve fonksiyon anahtarlarını** bulmak da mümkündür; bu, **`<app-name>`** klasöründeki JSON dosyalarında bulunabilir.
|
||||
|
||||
Fonksiyonların ayrıca kodu uzaktan bir konumda saklamaya da izin verdiğini unutmayın, sadece URL'yi belirtmeniz yeterlidir.
|
||||
Fonksiyonların ayrıca kodu uzaktan bir konumda saklamaya da izin verdiğini unutmayın; sadece URL'yi belirtmeniz yeterlidir.
|
||||
|
||||
### Ağ
|
||||
|
||||
HTTP tetikleyicisi kullanarak:
|
||||
Bir HTTP tetikleyici kullanarak:
|
||||
|
||||
- **Tüm İnternetten bir fonksiyona erişim** vermek mümkündür, herhangi bir kimlik doğrulama gerektirmeden veya IAM tabanlı erişim vermek mümkündür. Ancak bu erişimi kısıtlamak da mümkündür.
|
||||
- **Tüm İnternetten bir fonksiyona erişim** vermek mümkündür; bu, herhangi bir kimlik doğrulama gerektirmeden veya IAM tabanlı erişim vermeden yapılabilir. Ancak bu erişimi kısıtlamak da mümkündür.
|
||||
- **Bir iç ağdan (VPC)** bir Function App'a **erişim vermek veya kısıtlamak** da mümkündür.
|
||||
|
||||
> [!CAUTION]
|
||||
> Bu, bir saldırganın perspektifinden çok ilginçtir çünkü bir zayıf Fonksiyondan **iç ağlara geçiş** yapmak mümkün olabilir.
|
||||
> Bu, bir saldırganın, internete açık bir zayıf Fonksiyondan **iç ağlara geçiş yapma** olasılığı nedeniyle saldırganlar açısından çok ilginçtir.
|
||||
|
||||
### **Function App Ayarları ve Ortam Değişkenleri**
|
||||
|
||||
Bir uygulama içinde ortam değişkenlerini yapılandırmak mümkündür, bu değişkenler hassas bilgiler içerebilir. Ayrıca, varsayılan olarak **`AzureWebJobsStorage`** ve **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (diğerleri arasında) ortam değişkenleri oluşturulur. Bu değişkenler özellikle ilginçtir çünkü **uygulamanın verilerini içeren depolama hesabını kontrol etmek için TAM izinlere sahip hesap anahtarını** içerir. Bu ayarlar, Depolama Hesabından kodu çalıştırmak için de gereklidir.
|
||||
Bir uygulama içinde, hassas bilgileri içerebilecek ortam değişkenlerini yapılandırmak mümkündür. Ayrıca, varsayılan olarak **`AzureWebJobsStorage`** ve **`WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`** (diğerleri arasında) ortam değişkenleri oluşturulur. Bu değişkenler, **uygulamanın verilerini kontrol etmek için TAM izinlere sahip hesap anahtarını** içerdiği için özellikle ilginçtir. Bu ayarlar, Depolama Hesabından kodu çalıştırmak için de gereklidir.
|
||||
|
||||
Bu ortam değişkenleri veya yapılandırma parametreleri, Fonksiyonun kodu nasıl çalıştırdığını da kontrol eder; örneğin, **`WEBSITE_RUN_FROM_PACKAGE`** varsa, bu uygulamanın kodunun bulunduğu URL'yi belirtir.
|
||||
Bu ortam değişkenleri veya yapılandırma parametreleri, Fonksiyonun kodu nasıl çalıştırdığını da kontrol eder; örneğin, **`WEBSITE_RUN_FROM_PACKAGE`** varsa, bu, uygulamanın kodunun bulunduğu URL'yi belirtir.
|
||||
|
||||
### **Function Sandbox**
|
||||
|
||||
Linux sandbox içinde kaynak kodu **`/home/site/wwwroot`** dizininde **`function_app.py`** dosyasında (Python kullanılıyorsa) yer alır, kodu çalıştıran kullanıcı **`app`**'dir (sudo izinleri olmadan).
|
||||
Linux sandbox içinde kaynak kodu **`/home/site/wwwroot`** dizininde **`function_app.py`** dosyasında (Python kullanılıyorsa) bulunur; kodu çalıştıran kullanıcı **`app`**'dir (sudo izinleri olmadan).
|
||||
|
||||
Bir **Windows** fonksiyonunda NodeJS kullanıldığında kod **`C:\home\site\wwwroot\HttpTrigger1\index.js`** dizininde yer alıyordu, kullanıcı adı **`mawsFnPlaceholder8_f_v4_node_20_x86`** idi ve **grupların** bir parçasıydı: `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`.
|
||||
Bir **Windows** fonksiyonunda NodeJS kullanıldığında kod **`C:\home\site\wwwroot\HttpTrigger1\index.js`** dizininde bulunuyordu; kullanıcı adı **`mawsFnPlaceholder8_f_v4_node_20_x86`** idi ve **grupların** bir parçasıydı: `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`.
|
||||
|
||||
### **Yönetilen Kimlikler ve Meta Veriler**
|
||||
|
||||
[**VM'ler**](vms/index.html) gibi, Fonksiyonların **2 tür Yönetilen Kimliği** olabilir: Sistem atanan ve Kullanıcı atanan.
|
||||
|
||||
**Sistem atanan** olan, **yalnızca atanan fonksiyon** tarafından kullanılabilecek bir yönetilen kimliktir, **kullanıcı atanan** yönetilen kimlikler ise **herhangi bir diğer Azure hizmeti tarafından kullanılabilecek** yönetilen kimliklerdir.
|
||||
**Sistem atanan** olan, **yalnızca atanan fonksiyonun** kullanabileceği bir yönetilen kimliktir; **kullanıcı atanan** yönetilen kimlikler ise **herhangi bir diğer Azure hizmetinin kullanabileceği** yönetilen kimliklerdir.
|
||||
|
||||
> [!NOTE]
|
||||
> [**VM'ler**](vms/index.html) gibi, Fonksiyonların **1 sistem atanan** yönetilen kimliği ve **birkaç kullanıcı atanan** yönetilen kimliği olabilir, bu nedenle fonksiyonu tehlikeye attığınızda hepsini bulmaya çalışmak her zaman önemlidir çünkü yalnızca bir Fonksiyondan birkaç yönetilen kimliğe yetki yükseltebilirsiniz.
|
||||
> [**VM'ler**](vms/index.html) gibi, Fonksiyonların **1 sistem atanan** yönetilen kimliği ve **birden fazla kullanıcı atanan** yönetilen kimliği olabilir, bu nedenle fonksiyonu tehlikeye atarsanız hepsini bulmaya çalışmak her zaman önemlidir; çünkü tek bir Fonksiyondan birden fazla yönetilen kimliğe yetki yükseltebilirsiniz.
|
||||
>
|
||||
> Eğer bir sistem yönetilen kimliği kullanılmıyorsa ancak bir veya daha fazla kullanıcı yönetilen kimliği bir fonksiyona bağlıysa, varsayılan olarak herhangi bir token alamazsınız.
|
||||
|
||||
[**PEASS scriptlerini**](https://github.com/peass-ng/PEASS-ng) kullanarak varsayılan yönetilen kimlikten token alabilirsiniz. Ya da bunları **manuel olarak** alabilirsiniz, açıklamalar için:
|
||||
Varsayılan yönetilen kimlikten token almak için [**PEASS scriptlerini**](https://github.com/peass-ng/PEASS-ng) kullanmak mümkündür. Ya da bunları **manuel olarak** almak için aşağıdaki gibi açıklanan yöntemleri kullanabilirsiniz:
|
||||
|
||||
{% 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}}
|
||||
|
||||
Bir fonksiyonun bağlı olduğu **tüm Yönetilen Kimlikleri kontrol etmenin** bir yolunu bulmanız gerektiğini unutmayın, çünkü belirtmezseniz, meta veri uç noktası **yalnızca varsayılan olanı** kullanacaktır (daha fazla bilgi için önceki bağlantıya bakın).
|
||||
Bir fonksiyonun bağlı olduğu **tüm Yönetilen Kimlikleri kontrol etmenin** bir yolunu bulmanız gerektiğini unutmayın; çünkü belirtmezseniz, meta veri uç noktası **yalnızca varsayılan olanı** kullanacaktır (daha fazla bilgi için önceki bağlantıya bakın).
|
||||
|
||||
## Erişim Anahtarları
|
||||
|
||||
@@ -84,10 +86,10 @@ Bir fonksiyon içinde bir **HTTP tetikleyici** kullanarak bir uç nokta oluştur
|
||||
|
||||
**Anahtar türleri:**
|
||||
|
||||
- **Fonksiyon Anahtarları:** Fonksiyon anahtarları varsayılan veya kullanıcı tanımlı olabilir ve yalnızca bir Function App içindeki **belirli fonksiyon uç noktalarına** erişim sağlamak için tasarlanmıştır, bu da uç noktalar üzerinde daha ince ayarlı bir erişim sağlar.
|
||||
- **Fonksiyon Anahtarları:** Fonksiyon anahtarları varsayılan veya kullanıcı tanımlı olabilir ve yalnızca bir Function App içindeki **belirli fonksiyon uç noktalarına** erişim sağlamak için tasarlanmıştır; bu, uç noktalar üzerinde daha ince ayarlanmış bir erişim sağlar.
|
||||
- **Ana Bilgisayar Anahtarları:** Ana bilgisayar anahtarları, varsayılan veya kullanıcı tanımlı olabilir ve **FUNCTION erişim seviyesine sahip bir Function App içindeki tüm fonksiyon uç noktalarına** erişim sağlar.
|
||||
- **Master Anahtar:** Master anahtar (`_master`), tüm fonksiyon uç noktalarına (ADMIN erişim seviyesi dahil) erişim sağlayan yönetim anahtarıdır. Bu **anahtar iptal edilemez.**
|
||||
- **Sistem Anahtarları:** Sistem anahtarları, **belirli uzantılar tarafından yönetilir** ve iç bileşenler tarafından kullanılan webhook uç noktalarına erişim için gereklidir. Örnekler arasında, sistem anahtarlarını kullanarak ilgili API'leri güvenli bir şekilde etkileşimde bulunan Olay Grid tetikleyicisi ve Dayanıklı Fonksiyonlar bulunmaktadır.
|
||||
- **Sistem Anahtarları:** Sistem anahtarları, **belirli uzantılar tarafından yönetilir** ve iç bileşenler tarafından kullanılan webhook uç noktalarına erişim için gereklidir. Örnekler arasında, sistem anahtarlarını kullanarak ilgili API'leri güvenli bir şekilde etkileşimde bulunan Event Grid tetikleyicisi ve Dayanıklı Fonksiyonlar bulunur.
|
||||
|
||||
> [!TIP]
|
||||
> Bir anahtar kullanarak bir fonksiyon API uç noktasına erişim örneği:
|
||||
@@ -96,7 +98,7 @@ Bir fonksiyon içinde bir **HTTP tetikleyici** kullanarak bir uç nokta oluştur
|
||||
|
||||
### Temel Kimlik Doğrulama
|
||||
|
||||
Uygulama Hizmetlerinde olduğu gibi, Fonksiyonlar da **SCM** ve **FTP** ile bağlanmak için temel kimlik doğrulamayı destekler ve kodu dağıtmak için Azure tarafından sağlanan **bir URL'deki kullanıcı adı ve şifre** kullanır. Bununla ilgili daha fazla bilgi:
|
||||
Uygulama Hizmetlerinde olduğu gibi, Fonksiyonlar da **SCM** ve **FTP** ile bağlanmak için temel kimlik doğrulamayı destekler; bu, Azure tarafından sağlanan bir **kullanıcı adı ve şifreyi URL'de** kullanarak kod dağıtımına olanak tanır. Bununla ilgili daha fazla bilgi:
|
||||
|
||||
{{#ref}}
|
||||
az-app-services.md
|
||||
@@ -104,7 +106,7 @@ az-app-services.md
|
||||
|
||||
### Github Tabanlı Dağıtımlar
|
||||
|
||||
Bir fonksiyon bir Github deposundan oluşturulduğunda, Azure web konsolu **belirli bir depoda otomatik olarak bir Github İş Akışı oluşturmasına** olanak tanır, böylece bu depo güncellendiğinde fonksiyonun kodu da güncellenir. Aslında, bir Python fonksiyonu için Github Action yaml'ı şöyle görünür:
|
||||
Bir fonksiyon bir Github deposundan oluşturulduğunda, Azure web konsolu, bu depo güncellendiğinde fonksiyonun kodunun güncellenmesi için **belirli bir depoda otomatik olarak bir Github İş Akışı oluşturmayı** sağlar. Aslında, bir Python fonksiyonu için Github Action yaml'ı şöyle görünür:
|
||||
|
||||
<details>
|
||||
|
||||
@@ -192,16 +194,16 @@ package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}
|
||||
```
|
||||
</details>
|
||||
|
||||
Ayrıca, bir **Managed Identity** oluşturulur, böylece depodaki Github Action Azure'a bu kimlik ile giriş yapabilir. Bu, **Managed Identity** üzerinde bir Federated kimlik bilgisi oluşturarak yapılır ve **Issuer** `https://token.actions.githubusercontent.com` ile **Subject Identifier** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>` belirlenir.
|
||||
Ayrıca, bir **Managed Identity** oluşturulur, böylece depodaki Github Action Azure'a bu kimlik ile giriş yapabilir. Bu, **Managed Identity** üzerinde bir Federated kimlik bilgisi oluşturarak yapılır ve **Issuer** `https://token.actions.githubusercontent.com` ile **Subject Identifier** `repo:<org-name>/<repo-name>:ref:refs/heads/<branch-name>` izin verilir.
|
||||
|
||||
> [!CAUTION]
|
||||
> Bu nedenle, o depoyu ele geçiren herkes, işlevi ve ona bağlı Managed Identities'i ele geçirebilir.
|
||||
> Bu nedenle, o depoyu ele geçiren herkes, işlevi ve ona bağlı Managed Identities'i tehlikeye atabilecektir.
|
||||
|
||||
### Konteyner Tabanlı Dağıtımlar
|
||||
|
||||
Tüm planlar konteyner dağıtımına izin vermez, ancak izin verenler için yapılandırma konteynerin URL'sini içerecektir. API'de **`linuxFxVersion`** ayarı şöyle bir şey içerecektir: `DOCKER|mcr.microsoft.com/...`, web konsolunda ise yapılandırma **image settings** olarak gösterilecektir.
|
||||
Tüm planlar konteyner dağıtımına izin vermez, ancak izin verenler için yapılandırma konteynerin URL'sini içerecektir. API'de **`linuxFxVersion`** ayarı şöyle bir şey olacaktır: `DOCKER|mcr.microsoft.com/...`, web konsolunda ise yapılandırma **image settings** olarak gösterilecektir.
|
||||
|
||||
Ayrıca, **kaynak kodu depoya** kaydedilmeyecektir, çünkü bu gerekli değildir.
|
||||
Ayrıca, **fonksiyonla ilgili depolama** hesabında kaynak kodu saklanmayacaktır çünkü bu gerekli değildir.
|
||||
|
||||
## Enumeration
|
||||
|
||||
@@ -211,10 +213,10 @@ Ayrıca, **kaynak kodu depoya** kaydedilmeyecektir, çünkü bu gerekli değildi
|
||||
# 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
10
theme/elasticlunr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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 ────────────────────────────────────────────────
|
||||
Dual‑index Web‑Worker search (HackTricks + HackTricks‑Cloud)
|
||||
· 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. 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<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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
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={'&':'&','<':'<','>':'>','"':'"','\'':'''};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);
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user