Skip to content

Commit 06a0892

Browse files
committed
f
1 parent 8cb43f6 commit 06a0892

34 files changed

Lines changed: 135 additions & 111 deletions

File tree

scripts/seo_postprocess.py

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
"Kubernetes, CI/CD, and workspace pentesting techniques."
3939
),
4040
}
41+
SITE_ALTERNATE_NAMES = {
42+
"HackTricks Cloud": ["HT Cloud", "HackTricks Wiki Cloud"],
43+
}
44+
SITE_SAME_AS = {
45+
"HackTricks Cloud": [
46+
"https://github.com/HackTricks-wiki/hacktricks-cloud",
47+
"https://www.linkedin.com/company/hacktricks",
48+
"https://twitter.com/hacktricks_live",
49+
],
50+
}
4151
LANGUAGE_LOCALES = {
4252
"af": "af_ZA",
4353
"de": "de_DE",
@@ -163,10 +173,6 @@ def homepage_description(site_name):
163173
return SITE_DESCRIPTIONS.get(site_name, f"{site_name}: practical cloud security guides and references.")
164174

165175

166-
def strip_index_suffix(path):
167-
return re.sub(r"(?:^|/)index\.html$", "", path.as_posix())
168-
169-
170176
def is_homepage(rel_path):
171177
return rel_path.as_posix() == "index.html"
172178

@@ -211,52 +217,75 @@ def humanize_slug(value):
211217

212218
def breadcrumb_items(site_url, lang, rel_path):
213219
items = [{"name": "Home", "url": canonical_url(site_url, lang, Path("index.html"))}]
214-
bare_path = strip_index_suffix(rel_path)
215-
if not bare_path:
220+
if is_homepage(rel_path):
216221
return items
217222

218-
parts = [part for part in bare_path.split("/") if part]
219-
for idx in range(len(parts)):
220-
crumb_rel = Path(*parts[: idx + 1], "index.html")
221-
items.append({"name": humanize_slug(parts[idx]), "url": canonical_url(site_url, lang, crumb_rel)})
223+
if rel_path.name == "index.html":
224+
directory_parts = list(rel_path.parent.parts)
225+
page_name = None
226+
else:
227+
directory_parts = [] if rel_path.parent == Path(".") else list(rel_path.parent.parts)
228+
page_name = rel_path.name
229+
230+
for idx, part in enumerate(directory_parts):
231+
crumb_rel = Path(*directory_parts[: idx + 1], "index.html")
232+
items.append({"name": humanize_slug(part), "url": canonical_url(site_url, lang, crumb_rel)})
233+
234+
if page_name:
235+
items.append({"name": humanize_slug(page_name), "url": canonical_url(site_url, lang, rel_path)})
236+
222237
return items
223238

224239

225-
def build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages):
240+
def build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages, lastmod):
226241
current_url = canonical_url(site_url, lang, rel_path)
227242
site_root = site_url.rstrip("/")
228-
website_url = canonical_url(site_url, "en", Path("index.html"))
229-
data = [
230-
{
231-
"@context": "https://schema.org",
232-
"@type": "Organization",
233-
"@id": f"{site_root}/#organization",
234-
"name": site_name,
235-
"url": site_root,
236-
"logo": {"@type": "ImageObject", "url": image_url},
237-
},
238-
{
239-
"@context": "https://schema.org",
240-
"@type": "WebSite",
241-
"@id": f"{site_root}/#website",
242-
"url": site_root,
243-
"name": site_name,
244-
"inLanguage": languages,
245-
"publisher": {"@id": f"{site_root}/#organization"},
246-
},
247-
{
243+
data = []
244+
245+
if is_homepage(rel_path):
246+
data.extend(
247+
[
248+
{
249+
"@context": "https://schema.org",
250+
"@type": "Organization",
251+
"@id": f"{site_root}/#organization",
252+
"name": site_name,
253+
"alternateName": SITE_ALTERNATE_NAMES.get(site_name, []),
254+
"url": site_root,
255+
"description": homepage_description(site_name),
256+
"logo": {"@type": "ImageObject", "url": image_url},
257+
"sameAs": SITE_SAME_AS.get(site_name, []),
258+
},
259+
{
260+
"@context": "https://schema.org",
261+
"@type": "WebSite",
262+
"@id": f"{site_root}/#website",
263+
"url": site_root,
264+
"name": site_name,
265+
"alternateName": SITE_ALTERNATE_NAMES.get(site_name, []),
266+
"description": homepage_description(site_name),
267+
"inLanguage": languages,
268+
"publisher": {"@id": f"{site_root}/#organization"},
269+
},
270+
]
271+
)
272+
273+
data.extend(
274+
[
275+
{
248276
"@context": "https://schema.org",
249277
"@type": "WebPage",
250278
"@id": f"{current_url}#webpage",
251279
"url": current_url,
252280
"name": title,
253281
"description": description,
254282
"inLanguage": lang,
283+
"dateModified": lastmod,
255284
"isPartOf": {"@id": f"{site_root}/#website"},
256285
"about": {"@id": f"{site_root}/#organization"},
257286
"primaryImageOfPage": {"@type": "ImageObject", "url": image_url},
258-
},
259-
{
287+
},
288+
{
260289
"@context": "https://schema.org",
261290
"@type": "BreadcrumbList",
262291
"itemListElement": [
@@ -268,24 +297,18 @@ def build_structured_data(site_url, lang, rel_path, title, description, site_nam
268297
}
269298
for index, item in enumerate(breadcrumb_items(site_url, lang, rel_path), start=1)
270299
],
271-
},
272-
]
273-
274-
if is_homepage(rel_path):
275-
data[1]["potentialAction"] = {
276-
"@type": "SearchAction",
277-
"target": f"{website_url}?search={{search_term_string}}",
278-
"query-input": "required name=search_term_string",
279-
}
300+
},
301+
]
302+
)
280303

281304
return data
282305

283306

284-
def build_seo_block(site_url, lang, rel_path, languages, default_lang, title, description, site_name):
307+
def build_seo_block(site_url, lang, rel_path, languages, default_lang, title, description, site_name, lastmod):
285308
current_url = canonical_url(site_url, lang, rel_path)
286309
image_url = social_image_url(site_url)
287310
structured_data = json.dumps(
288-
build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages),
311+
build_structured_data(site_url, lang, rel_path, title, description, site_name, image_url, languages, lastmod),
289312
ensure_ascii=False,
290313
separators=(",", ":"),
291314
)
@@ -335,13 +358,13 @@ def replace(match):
335358
return LANGUAGE_MENU_LINK_RE.sub(replace, document)
336359

337360

338-
def update_document(document, site_url, lang, rel_path, languages, default_lang, site_name):
361+
def update_document(document, site_url, lang, rel_path, languages, default_lang, site_name, lastmod):
339362
title_match = re.search(r"<title>(.*?)</title>", document, flags=re.I | re.S)
340363
page_title = clean_text(title_match.group(1)) if title_match else site_name
341364
fallback_description = f"{site_name}: {page_title}"
342365
description = homepage_description(site_name) if is_homepage(rel_path) else extract_description(document, fallback_description)
343366
seo_block = build_seo_block(
344-
site_url, lang, rel_path, languages, default_lang, page_title, description, site_name
367+
site_url, lang, rel_path, languages, default_lang, page_title, description, site_name, lastmod
345368
)
346369

347370
document = re.sub(
@@ -457,6 +480,7 @@ def process_pages(args):
457480
for html_file in iter_html_files(book_dir):
458481
rel_path = html_file.relative_to(book_dir)
459482
content = html_file.read_text(encoding="utf-8")
483+
lastmod = page_lastmod(book_dir, rel_path, html_file)
460484
updated = update_document(
461485
content,
462486
args.site_url,
@@ -465,6 +489,7 @@ def process_pages(args):
465489
languages,
466490
args.default_lang,
467491
args.site_name,
492+
lastmod,
468493
)
469494
html_file.write_text(updated, encoding="utf-8")
470495

src/pentesting-ci-cd/apache-airflow-security/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ If you have **access to the web console** you might be able to access some or al
6262
Variables can be stored in Airflow so the **DAGs** can **access** their values. It's similar to secrets of other platforms. If you have **enough permissions** you can access them in the GUI in `http://<airflow>/variable/list/`.\
6363
Airflow by default will show the value of the variable in the GUI, however, according to [**this**](https://marclamberti.com/blog/variables-with-apache-airflow/) it's possible to set a **list of variables** whose **value** will appear as **asterisks** in the **GUI**.
6464

65-
![](<../../images/image (164).png>)
65+
![Airflow Variables page showing AWS_ACCESS_KEY_ID and a masked AWS_SECRET_ACCESS_KEY](<../../images/image (164).png>)
6666

6767
However, these **values** can still be **retrieved** via **CLI** (you need to have DB access), **arbitrary DAG** execution, **API** accessing the variables endpoint (the API needs to be activated), and **even the GUI itself!**\
6868
To access those values from the GUI just **select the variables** you want to access and **click on Actions -> Export**.\
6969
Another way is to perform a **bruteforce** to the **hidden value** using the **search filtering** it until you get it:
7070

71-
![](<../../images/image (152).png>)
71+
![Airflow Variables search filter returning the AWS_SECRET_ACCESS_KEY variable](<../../images/image (152).png>)
7272

7373
#### Privilege Escalation
7474

src/pentesting-ci-cd/atlantis-security.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
Atlantis basically helps you to to run terraform from Pull Requests from your git server.
88

9-
![](<../images/image (161).png>)
9+
![Atlantis pull request workflow showing plan and apply comments in the PR lifecycle](<../images/image (161).png>)
1010

1111
### Local Lab
1212

@@ -304,7 +304,7 @@ Moreover, if you don't have configured in the **branch protection** to ask to **
304304
305305
This is the **setting** in Github branch protections:
306306
307-
![](<../images/image (216).png>)
307+
![GitHub branch protection option to dismiss stale pull request approvals after new commits](<../images/image (216).png>)
308308
309309
#### Webhook Secret
310310

src/pentesting-ci-cd/circleci-security.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
These are **secrets** that are only going to be **accessible** by the **project** (by **any branch**).\
7171
You can see them **declared in** _https://app.circleci.com/settings/project/github/\<org_name>/\<repo_name>/environment-variables_
7272
73-
![](<../images/image (129).png>)
73+
![CircleCI project environment variables page with MY_ENV_VAR and a masked secret value](<../images/image (129).png>)
7474
7575
> [!CAUTION]
7676
> The "**Import Variables**" functionality allows to **import variables from other projects** to this one.
@@ -79,7 +79,7 @@ You can see them **declared in** _https://app.circleci.com/settings/project/gith
7979
8080
These are secrets that are **org wide**. By **default any repo** is going to be able to **access any secret** stored here:
8181
82-
![](<../images/image (123).png>)
82+
![CircleCI context security group page showing All members allowed to execute the context](<../images/image (123).png>)
8383
8484
> [!TIP]
8585
> However, note that a different group (instead of All members) can be **selected to only give access to the secrets to specific people**.\

src/pentesting-ci-cd/concourse-security/concourse-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
### Architecture
1212

13-
![](<../../images/image (187).png>)
13+
![Concourse architecture diagram with load balancer, ATC, TSA, Beacon, Garden, and Baggageclaim components](<../../images/image (187).png>)
1414

1515
#### ATC: web UI & build scheduler
1616

src/pentesting-ci-cd/gitea-security/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
**Gitea** is a **self-hosted community managed lightweight code hosting** solution written in Go.
88

9-
![](<../../images/image (160).png>)
9+
![Gitea repository page showing files, branches, commits, tags, and repository statistics](<../../images/image (160).png>)
1010

1111
### Basic Information
1212

src/pentesting-ci-cd/gitea-security/basic-gitea-information.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ When creating a new team, several important settings are selected:
3434
- **Administrator** access
3535
- **Specific** access:
3636

37-
![](<../../images/image (118).png>)
37+
![Gitea organization repository permission matrix for owner, contributor, reader, and access roles](<../../images/image (118).png>)
3838

3939
### Teams & Users
4040

@@ -66,7 +66,7 @@ You can generate personal access token to **give an application access to your a
6666

6767
Just like personal access tokens **Oauth applications** will have **complete access** over your account and the places your account has access because, as indicated in the [docs](https://docs.gitea.io/en-us/oauth2-provider/#scopes), scopes aren't supported yet:
6868

69-
![](<../../images/image (194).png>)
69+
![Gitea OAuth authorization prompt for TestApp requesting full account and organization access](<../../images/image (194).png>)
7070

7171
### Deploy keys
7272

src/pentesting-ci-cd/jenkins-security/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Without credentials you can look inside _**/asynchPeople/**_ path or _**/securit
2828

2929
You may be able to get the Jenkins version from the path _**/oops**_ or _**/error**_
3030

31-
![](<../../images/image (146).png>)
31+
![Jenkins Oops error page exposing the Jenkins version in the footer](<../../images/image (146).png>)
3232

3333
### Known Vulnerabilities
3434

@@ -121,7 +121,7 @@ If the plugin reuses stored creds, Jenkins will attempt to authenticate to `atta
121121

122122
If the compromised user has **enough privileges to create/modify a new Jenkins node** and SSH credentials are already stored to access other nodes, he could **steal those credentials** by creating/modifying a node and **setting a host that will record the credentials** without verifying the host key:
123123

124-
![](<../../images/image (218).png>)
124+
![Jenkins node configuration form with host, credentials, and non-verifying host key strategy fields](<../../images/image (218).png>)
125125

126126
You will usually find Jenkins ssh credentials in a **global provider** (`/credentials/`), so you can also dump them as you would dump any other secret. More information in the [**Dumping secrets section**](#dumping-secrets).
127127

@@ -163,7 +163,7 @@ To exploit pipelines you still need to have access to Jenkins.
163163

164164
**Pipelines** can also be used as **build mechanism in projects**, in that case it can be configured a **file inside the repository** that will contains the pipeline syntax. By default `/Jenkinsfile` is used:
165165

166-
![](<../../images/image (127).png>)
166+
![Jenkins pipeline build configuration using Jenkinsfile mode and script path](<../../images/image (127).png>)
167167

168168
It's also possible to **store pipeline configuration files in other places** (in other repositories for example) with the goal of **separating** the repository **access** and the pipeline access.
169169

@@ -278,7 +278,7 @@ basic-jenkins-information.md
278278
279279
You can enumerate the **configured nodes** in `/computer/`, you will usually find the \*\*`Built-In Node` \*\* (which is the node running Jenkins) and potentially more:
280280
281-
![](<../../images/image (249).png>)
281+
![Jenkins node list showing agent1 and Built-In Node executors](<../../images/image (249).png>)
282282
283283
It is **specially interesting to compromise the Built-In node** because it contains sensitive Jenkins information.
284284
@@ -358,7 +358,7 @@ You can list the secrets accessing `/credentials/` if you have enough permission
358358
359359
If you can **see the configuration of each project**, you can also see in there the **names of the credentials (secrets)** being use to access the repository and **other credentials of the project**.
360360
361-
![](<../../images/image (180).png>)
361+
![Jenkins credentials selector showing gitea-access-token and Add credential button](<../../images/image (180).png>)
362362
363363
#### From Groovy
364364

src/pentesting-ci-cd/jenkins-security/basic-jenkins-information.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ In `/configureSecurity` it's possible to **configure the authorization method of
3333
- **Logged-in users can do anything**: In this mode, every **logged-in user gets full control** of Jenkins. The only user who won't have full control is **anonymous user**, who only gets **read access**.
3434
- **Matrix-based security**: You can configure **who can do what** in a table. Each **column** represents a **permission**. Each **row** **represents** a **user or a group/role.** This includes a special user '**anonymous**', which represents **unauthenticated users**, as well as '**authenticated**', which represents **all authenticated users**.
3535

36-
![](<../../images/image (149).png>)
36+
![Jenkins matrix-based authorization table with permissions columns for users and groups](<../../images/image (149).png>)
3737

3838
- **Project-based Matrix Authorization Strategy:** This mode is an **extension** to "**Matrix-based security**" that allows additional ACL matrix to be **defined for each project separately.**
3939
- **Role-Based Strategy:** Enables defining authorizations using a **role-based strategy**. Manage the roles in `/role-strategy`.

src/pentesting-ci-cd/jenkins-security/jenkins-rce-creating-modifying-pipeline.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
In "New Item" (accessible in `/view/all/newJob`) select **Pipeline:**
88

9-
![](<../../images/image (235).png>)
9+
![Jenkins New Item page with Pipeline selected as the project type](<../../images/image (235).png>)
1010

1111
In the **Pipeline section** write the **reverse shell**:
1212

13-
![](<../../images/image (285).png>)
13+
![Jenkins Pipeline script editor containing a Groovy reverse shell payload](<../../images/image (285).png>)
1414

1515
```groovy
1616
pipeline {
@@ -30,7 +30,7 @@ pipeline {
3030

3131
Finally click on **Save**, and **Build Now** and the pipeline will be executed:
3232

33-
![](<../../images/image (228).png>)
33+
![Jenkins build console showing a reverse shell connection and whoami output](<../../images/image (228).png>)
3434

3535
## Modifying a Pipeline
3636

0 commit comments

Comments
 (0)