Хакер - Секреты PowerShell. Пишем HTTP-запросы и парсим страницы на PowerShell

Хакер - Секреты PowerShell. Пишем HTTP-запросы и парсим страницы на PowerShell

hacker_frei

https://t.me/hacker_frei

Андрей Попов

Содержание статьи

  • Командлет Invoke-WebRequest
  • Анализ HTML-страниц
  • Выполнение POST-запросов
  • Командлет Invoke-RestMethod
  • Итоги

В интерне­те есть мно­жес­тво сер­висов, с которы­ми мож­но работать, обра­щаясь к их ресур­сам по про­токо­лу HTTP. Веб‑раз­работ­чики пос­тоян­но работа­ют с такими HTTP-зап­росами для дос­тупа к фун­кци­ям внеш­них API или для тес­тирова­ния собс­твен­ных при­ложе­ний. PowerShell для обра­щения к вебу по HTTP пред­лага­ет два стан­дар­тных коман­дле­та: Invoke-WebRequest и Invoke-RestMethod.

КОМАНДЛЕТ INVOKE-WEBREQUEST

С помощью коман­дле­та Invoke-WebRequest мож­но нап­равить веб‑сер­веру HTTP-зап­рос и получить от него ответ.

Анализ HTML-страниц

Этот коман­длет хорошо под­ходит для ана­лиза HTML-стра­ниц. Еще он уме­ет сох­ранять стра­ницы на локаль­ном дис­ке. В этом он похож на кон­соль­ную ути­литу wget и даже име­ет такой псев­доним:

PS C:\Script> Get-Alias wget

CommandType Name

----------- ----

Alias wget -> Invoke-WebRequest

Об­ратим­ся с помощью Invoke-WebRequest к какой‑нибудь прос­той стра­нице, нап­ример Example Domain.

По умол­чанию Invoke-WebRequest выпол­няет HTTP-зап­рос с методом GET к ресур­су на веб‑сер­вере, адрес ресур­са ука­зыва­ется в качес­тве зна­чения парамет­ра -Uri. В резуль­тате воз­вра­щает­ся объ­ект типа HtmlWebResponseObject, в котором хра­нит­ся информа­ция об отве­те сер­вера:

PS C:\Script> $web = Invoke-WebRequest -Uri https://example.com/index.html

PS C:\Script> $web | Get-Member

TypeName: Microsoft.PowerShell.Commands.HtmlWebResponseObject

. . .

PS C:\Script> $web

StatusCode : 200

StatusDescription : OK

Content : <!doctype html>

<html>

<head>

<title>Example Domain</title>

<meta charset="utf-8" />

<meta http-equiv="Content-type" content="text/html; char

set=utf-8" />

<meta name="viewport" conten...

RawContent : HTTP/1.1 200 OK

Age: 497890

Vary: Accept-Encoding

X-Cache: HIT

Content-Length: 1256

Cache-Control: max-age=604800

Content-Type: text/html; charset=UTF-8

Date: Mon, 12 Jul 2021 16:05:14 GMT

Exp...

Forms : {}

Headers : {[Age, 497890], [Vary, Accept-Encoding], [X-Cache, HIT], [Co

ntent-Length, 1256]...}

Images : {}

InputFields : {}

Links : {@{innerHTML=More information...; innerText=More information

...; outerHTML=<A href="https://www.iana.org/domains/example

">More information...</A>; outerText=More information...; ta

gName=A; href=https://www.iana.org/domains/example}}

ParsedHtml : mshtml.HTMLDocumentClass

RawContentLength : 1256

В поле StatusCode содер­жится код отве­та от сер­вера (200 для нашего при­мера), в поле StatusDescription — тек­сто­вое опи­сание это­го отве­та (OK).

Современный PowerShell

Эта статья — гла­ва из кни­ги Андрея Попова «Сов­ремен­ный PowerShell», вышед­шей в изда­тель­стве «БХВ» в мар­те это­го года. В кни­ге под­робно опи­сан язык PowerShell и работа с обо­лоч­кой Windows PowerShell в Windows Terminal, вза­имо­дей­ствие с фай­ловой сис­темой, струк­туриро­ван­ными дан­ными и веб‑ресур­сами. Автор раз­бира­ет управле­ние про­цес­сами, служ­бами и сер­верами авто­мати­зации, рас­ска­зыва­ет, как соз­дать GUI для сце­нари­ев Windows PowerShell, уде­ляет вни­мание кросс‑плат­формен­ным воз­можнос­тям PowerShell в macOS и Linux.

Ес­ли ты адми­нис­три­руешь рабочие стан­ции, сер­веры или локаль­ные сети под управле­нием Windows, кни­га «Сов­ремен­ный PowerShell» поможет тебе осво­ить сек­реты и хит­рости этой тех­нологии и авто­мати­зиро­вать мно­жес­тво рутин­ных задач.

Содержимое ответа от сервера и HTTP-заголовки

Со­дер­жимое отве­та от сер­вера хра­нит­ся в виде стро­ки в поле Content. В нашем слу­чае здесь будет записан HTML-код:

PS C:\Script> $web.Content

<!doctype html>

<html>

<head>

<title>Example Domain</title>

<meta charset="utf-8" />

<meta http-equiv="Content-type" content="text/html; charset=utf-8" />

<meta name="viewport" content="width=device-width, initial-scale=1" />

<style type="text/css">

body {

background-color: #f0f0f2;

margin: 0;

padding: 0;

font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

}

div {

width: 600px;

margin: 5em auto;

padding: 2em;

background-color: #fdfdff;

border-radius: 0.5em;

box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);

}

a:link, a:visited {

color: #38488f;

text-decoration: none;

}

@media (max-width: 700px) {

div {

margin: 0 auto;

width: auto;

}

}

</style>

</head>

<body>

<div>

<h1>Example Domain</h1>

<p>This domain is for use in illustrative examples in documents. You may use this

domain in literature without prior coordination or asking for permission.</p>

<p><a href="https://www.iana.org/domains/example">More information...</a></p>

</div>

</body>

</html>

В поле RawContent записы­вает­ся пол­ный ответ от сер­вера с HTTP-заголов­ками в начале:

PS C:\Script> $web.RawContent

HTTP/1.1 200 OK

Age: 497890

Vary: Accept-Encoding

X-Cache: HIT

Content-Length: 1256

Cache-Control: max-age=604800

Content-Type: text/html; charset=UTF-8

Date: Mon, 12 Jul 2021 16:05:14 GMT

Expires: Mon, 19 Jul 2021 16:05:14 GMT

ETag: "3147526947+ident"

Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT

Server: ECS (dcb/7F83)

<!doctype html>

<html>

<head>

<title>Example Domain</title>

. . .

За­голов­ки отве­та тоже хра­нят­ся отдель­но — в виде хеш‑таб­лицы в свой­стве headers:

PS C:\Script> $web.headers

Key Value

--- -----

Age 497890

Vary Accept-Encoding

X-Cache HIT

Content-Length 1256

Cache-Control max-age=604800

Content-Type text/html; charset=UTF-8

Date Mon, 12 Jul 2021 16:05:14 GMT

Expires Mon, 19 Jul 2021 16:05:14 GMT

ETag "3147526947+ident"

Last-Modified Thu, 17 Oct 2019 07:18:26 GMT

Server ECS (dcb/7F83)

Сохранение веб-ресурсов

Для сох­ранения отве­та от сер­вера в виде локаль­ного фай­ла надо при вызове Invoke-WebRequest исполь­зовать ключ -OutFile и ука­зать путь к нуж­ному фай­лу. Нап­ример, сох­раним стра­ницу https://example.com/index.html в фай­ле page.html в текущем катало­ге:

PS C:\Script> Invoke-WebRequest -Uri https://example.com/index.html -OutFile page.html

Про­верим содер­жимое фай­ла page.html и убе­дим­ся, что в нем записа­на HTML-раз­метка сох­ранен­ной стра­ницы:

PS C:\Script> type .\page.html

<!doctype html>

<html>

<head>

<title>Example Domain</title>

<meta charset="utf-8" />

<meta http-equiv="Content-type" content="text/html; charset=utf-8" />

<meta name="viewport" content="width=device-width, initial-scale=1" />

<style type="text/css">

body {

background-color: #f0f0f2;

margin: 0;

padding: 0;

font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

}

div {

width: 600px;

margin: 5em auto;

padding: 2em;

background-color: #fdfdff;

border-radius: 0.5em;

box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);

}

a:link, a:visited {

color: #38488f;

text-decoration: none;

}

@media (max-width: 700px) {

div {

margin: 0 auto;

width: auto;

}

}

</style>

</head>

<body>

<div>

<h1>Example Domain</h1>

<p>This domain is for use in illustrative examples in documents. You may use this

domain in literature without prior coordination or asking for permission.</p>

<p><a href="https://www.iana.org/domains/example">More information...</a></p>

</div>

</body>

</html>

По­доб­ным обра­зом мож­но сох­ранять не толь­ко HTML-фай­лы, но и ресур­сы дру­гих типов (тек­сто­вые, гра­фичес­кие или муль­тимедий­ные фай­лы и т. д.), к которым мож­но обра­тить­ся на сай­те.

Поиск HTML-элементов на странице

В свой­ствах Forms (фор­мы), Images (изоб­ражения), InputFields (поля вво­да) и Links (ссыл­ки) объ­екта HtmlWebResponseObject сох­раня­ются мас­сивы объ­ектов, которые опи­сыва­ют соот­ветс­тву­ющие эле­мен­ты HTML-раз­метки, получен­ной от сер­вера. Обра­баты­вая эти кол­лекции, мож­но получить информа­цию об инте­ресу­ющих нас эле­мен­тах на заг­ружен­ной стра­нице.

Нап­ример, сох­раним в перемен­ной $web стра­ницу резуль­татов поис­ка сло­ва PowerShell в Yandex:

PS C:\Script> $web = Invoke-WebRequest -Uri https://yandex.ru/search/?text=PowerShell

Вы­делим на этой стра­нице все ссыл­ки на сайт habr.com. Для это­го нуж­но отфиль­тро­вать мас­сив $web.Links, оста­вив в нем объ­екты, у которых зна­чение свой­ства href соот­ветс­тву­ет мас­ке *habr.com*:

PS C:\Script> $habr_links = $web.Links | Where-Object href -like '*habr.com*'

В $habr_links находят­ся два объ­екта PSCustomObject, содер­жащие раз­личные HTML-атри­буты для ссы­лок:

PS C:\Script> $habr_links.count

2

PS C:\Script> $habr_links | Get-Member

TypeName: System.Management.Automation.PSCustomObject

Name MemberType Definition

---- ---------- ----------

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetType Method type GetType()

ToString Method string ToString()

class NoteProperty string class=Link Link_theme_normal OrganicTitl...

data-counter NoteProperty string data-counter=["b"]

data-log-node NoteProperty string data-log-node=bf7fw01-00

href NoteProperty string href=https://habr.com/ru/company/ruvds/b...

innerHTML NoteProperty string innerHTML=<div class="Favicon Favicon_si...

innerText NoteProperty string innerText=...

outerHTML NoteProperty string outerHTML=<a tabindex="0" class="Link Li...

outerText NoteProperty string outerText=...

tabindex NoteProperty string tabindex=0

tagName NoteProperty string tagName=A

target NoteProperty string target=_blank

Нап­ример, выведем зна­чение атри­бутов href и innerText для этих ссы­лок:

PS C:\Script> $habr_links | ForEach-Object {$_.href + " - " + $_.innerText }

https://habr.com/ru/company/ruvds/blog/487876/ -

Что такое Windows PowerShell и с чем его едят? / Хабр

https://habr.com/ru/company/ruvds/blog/487876/ - habr.com›ru/company/ruvds/blog/487876/

В свой­стве ParsedHtml объ­екта HtmlWebResponseObject содер­жится объ­ект типа mshtml.HTMLDocumentClass, который пре­дос­тавля­ет дос­туп к DOM-дереву заг­ружен­ной HTML-стра­ницы.

PS C:\Users\andrv> $html = $web.ParsedHtml

PS C:\Users\andrv> Get-Member -InputObject $html

TypeName: mshtml.HTMLDocumentClass

. . .

INFO

Ес­ли в HTML-коде име­ются сце­нарии JavaScript, то по умол­чанию при пос­тро­ении DOM-дерева они будут выпол­нены. При необ­ходимос­ти выпол­нение сце­нари­ев мож­но отклю­чить, ука­зав параметр -UseBasicParsing.

В час­тнос­ти, исполь­зуя методы getElementById()getElementsByName() и getElementsByTagName(), мож­но получать объ­екты, соот­ветс­тву­ющие HTML-эле­мен­там с задан­ным иден­тифика­тором, име­нем или тегом соот­ветс­твен­но.

Нап­ример, пос­мотрим, какой текст записан в пер­вом заголов­ке вто­рого уров­ня (HTML-тег <h2>):

PS C:\Users\andrv> $html.getElementsByTagName('h2')[0].innerText

Документация по PowerShell - PowerShell | Microsoft Docs

Най­дем эле­мент с иден­тифика­тором search-result:

PS C:\Users\andrv> $html.getElementById('search-result')

className : serp-list serp-list_left_yes

id : search-result

tagName : UL

parentElement : System.__ComObject

style : System.__ComObject

. . .

Ме­тод querySelector() поз­воля­ет най­ти HTML-эле­мент по опре­делен­ному CSS-селек­тору. Нап­ример, обра­тим­ся к эле­мен­ту с клас­сом main__content:

PS C:\Users\andrv> $html.querySelector('.main__content')

className : main__content

id :

tagName : DIV

parentElement : System.__ComObject

style : System.__ComObject

. . .

Под­робнее о свой­ствах, методах и событи­ях объ­екта mshtml.HTMLDocumentClass мож­но про­читать в до­кумен­тации на сай­те Microsoft.

Выполнение POST-запросов

Ко­ман­длет Invoke-WebRequest поз­воля­ет не толь­ко выпол­нять GET-зап­росы, но и вызывать дру­гие методы, опре­делен­ные в про­токо­ле HTTP (DELETEHEADMERGEPATCHPOSTPUTTRACE). Для это­го нуж­ный метод ука­зыва­ется в качес­тве зна­чения парамет­ра -Method.

Рас­смот­рим при­мер выпол­нения зап­роса с HTTP-методом POST, который час­то исполь­зует­ся для переда­чи дан­ных из веб‑форм или заг­рузки фай­лов на сер­вер. Обра­щать­ся мы будем к ресур­су http://httpbin.org/post, в резуль­тате сер­вер дол­жен сооб­щить нам о получен­ных дан­ных.

Пе­реда­вать мы будем два парамет­ра с име­нами name и lastName, которые помес­тим в хеш‑таб­лицу $params:

PS C:\Users\andrv> $params = @{name='Andrey'; lastName='Popov'}

Вы­пол­ним Invoke-WebRequest с методом POST, помес­тив переда­ваемые парамет­ры в тело зап­роса (параметр -Body):

PS C:\Users\andrv> Invoke-WebRequest -Uri http://httpbin.org/post -Method POST -Body $params

StatusCode : 200

StatusDescription : OK

Content : {

"args": {},

"data": "",

"files": {},

"form": {

"lastName": "Popov",

"name": "Andrey"

},

"headers": {

"Content-Length": "26",

"Content-Type": "application/x-www-form..

.

RawContent : HTTP/1.1 200 OK

Connection: keep-alive

Access-Control-Allow-Origin: *

Access-Control-Allow-Credentials: true

Content-Length: 503

Content-Type: application/json

Date: Fri, 16 Jul 2021 03:03:13 GM...

Forms : {}

Headers : {[Connection, keep-alive], [Access-Control-Al

low-Origin, *], [Access-Control-Allow-Credent

ials, true], [Content-Length, 503]...}

Images : {}

InputFields : {}

Links : {}

ParsedHtml : mshtml.HTMLDocumentClass

RawContentLength : 503

От­прав­ляемые на сер­вер парамет­ры коман­длет Invoke-WebRequest авто­мати­чес­ки при­водит к фор­мату application/x-www-form-urlencoded, который исполь­зует­ся при переда­че дан­ных из веб‑форм. Ответ в поле Content говорит о том, что сер­вер при­нял наши парамет­ры и опре­делил, что они были отправ­лены из фор­мы.

Иног­да быва­ет нуж­но переда­вать на сер­вер дан­ные в фор­мате JSON, а не в application/x-www-form-urlencoded. В этом слу­чае сле­дует ука­зать параметр -ContentType со зна­чени­ем application/json. Нап­ример:

PS C:\Users\andrv> $json_params = "{ 'name':'Andrey', 'lastName':'Popov' }"

PS C:\Users\andrv> Invoke-WebRequest -Uri http://httpbin.org/post -ContentType "application/json" -Method POST -Body $json_params

StatusCode : 200

StatusDescription : OK

Content : {

"args": {},

"data": "{ 'name':'Andrey', 'lastName':'Popov

' }",

"files": {},

"form": {},

"headers": {

"Content-Length": "39",

"Content-Type": "application/json",

"Host": "...

RawContent : HTTP/1.1 200 OK

Connection: keep-alive

Access-Control-Allow-Origin: *

Access-Control-Allow-Credentials: true

Content-Length: 475

Content-Type: application/json

Date: Fri, 16 Jul 2021 03:43:04 GM...

Forms : {}

Headers : {[Connection, keep-alive], [Access-Control-Allo

w-Origin, *], [Access-Control-Allow-Credentials

, true], [Content-Length, 475]...}

Images : {}

InputFields : {}

Links : {}

ParsedHtml : mshtml.HTMLDocumentClass

RawContentLength : 475

Как видим, при таком спо­собе отправ­ки зап­роса сер­верный скрипт, обра­баты­вающий обра­щения к ресур­су http://httpbin.org/post, извлек получен­ные дан­ные и при фор­мирова­нии сво­его JSON-отве­та помес­тил их в поле data, а не в поле form.

КОМАНДЛЕТ INVOKE-RESTMETHOD

Ес­ли мы обра­щаем­ся к веб‑сер­вису, под­держи­вающе­му REST API, то ответ сер­вера, ско­рее все­го, будет содер­жать струк­туриро­ван­ные дан­ные в фор­мате JSON или XML. Так, в пре­дыду­щем при­мере мы с помощью коман­дле­та Invoke-WebReques посыла­ли POST-зап­рос на ресурс http://httpbin.org/post и получа­ли в ответ JSON-стро­ку:

PS C:\Users\andrv> $web = Invoke-WebRequest -Uri http://httpbin.org/post -Method POST -Body @{name='Andrey'; lastName='Popov'}

PS C:\Users\andrv> $web.Content

{

"args": {},

"data": "",

"files": {},

"form": {

"lastName": "Popov",

"name": "Andrey"

},

"headers": {

"Content-Length": "26",

"Content-Type": "application/x-www-form-urlencoded",

"Host": "httpbin.org",

"User-Agent": "Mozilla/5.0 (Windows NT; Windows NT 10.0; ru-RU) WindowsPowerShell/5.1.19041.1023",

"X-Amzn-Trace-Id": "Root=1-60f2a42c-18f152db3b76b5d66d173e31"

},

"json": null,

"origin": "85.95.179.209",

"url": "http://httpbin.org/post"

}

Что­бы работать с полями отве­та, мы дол­жны пре­обра­зовать JSON в объ­ект PowerShell с помощью коман­дле­та ConvertFrom-Json:

PS C:\Users\andrv> $response = $web.Content | ConvertFrom-Json

PS C:\Users\andrv> $response

args :

data :

files :

form : @{lastName=Popov; name=Andrey}

headers : @{Content-Length=26; Content-Type=application/x-www-form-urlencoded; Host=htt

pbin.org; User-Agent=Mozilla/5.0 (Windows NT; Windows NT 10.0; ru-RU) Windows

PowerShell/5.1.19041.1023; X-Amzn-Trace-Id=Root=1-60f2a42c-18f152db3b76b5d66d

173e31}

json :

origin : 85.95.179.209

url : http://httpbin.org/post

PS C:\Users\andrv> $response.form.name

Andrey

В подоб­ных слу­чаях, ког­да от сер­вера мы получа­ем струк­туриро­ван­ные дан­ные, удоб­нее поль­зовать­ся коман­дле­том Invoke-RestMethod, который дей­ству­ет ана­логич­но Invoke-WebRequest и име­ет такие же парамет­ры, но при этом авто­мати­чес­ки пре­обра­зует ответ от сер­вера в объ­ект PowerShell.

Вы­пол­ним наш зап­рос с помощью Invoke-RestMethod, сох­ранив резуль­тат в перемен­ной $result:

PS C:\Users\andrv> $result = Invoke-RestMethod -Uri http://httpbin.org/post -Method POST -Body @{name='Andrey'; lastName='Popov'}

Про­верим тип и содер­жимое перемен­ной $result:

PS C:\Users\andrv> Get-Member -InputObject $result

TypeName: System.Management.Automation.PSCustomObject

Name MemberType Definition

---- ---------- ----------

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetType Method type GetType()

ToString Method string ToString()

args NoteProperty System.Management.Automation.PSCusto...

data NoteProperty string data=

files NoteProperty System.Management.Automation.PSCusto...

form NoteProperty System.Management.Automation.PSCusto...

headers NoteProperty System.Management.Automation.PSCusto...

json NoteProperty object json=null

origin NoteProperty string origin=85.95.179.209

url NoteProperty string url=http://httpbin.org/post

PS C:\Users\andrv> $result

args :

data :

files :

form : @{lastName=Popov; name=Andrey}

headers : @{Content-Length=26; Content-Type=application/x-www-form-urlencoded; Host=htt

pbin.org; User-Agent=Mozilla/5.0 (Windows NT; Windows NT 10.0; ru-RU) Windows

PowerShell/5.1.19041.1023; X-Amzn-Trace-Id=Root=1-60f2a897-6354ce272644b6d412

560a9b}

json :

origin : 85.95.179.209

url : http://httpbin.org/post

Как видим, вмес­то стро­ки в фор­мате JSON мы получа­ем PowerShell-объ­ект типа System.Management.Automation.PSCustomObject и можем сра­зу обра­щать­ся к нуж­ным свой­ствам:

PS C:\Users\andrv> $result.form.lastName

Popov

ИТОГИ

Итак, для обра­щения к веб‑ресур­сам по про­токо­лу HTTP в PowerShell име­ются два стан­дар­тных коман­дле­та: Invoke-WebRequest и Invoke-RestMethod. Для выпол­нения HTTP-зап­росов к веб‑ресур­сам, воз­вра­щающим HTML-стра­ницы, удоб­нее исполь­зовать коман­длет Invoke-WebRequest, а для работы с внеш­ними REST API, воз­вра­щающи­ми струк­туриро­ван­ные дан­ные, луч­ше подой­дет коман­длет Invoke-RestMethod.

Дей­ству­ют эти коман­дле­ты ана­логич­но друг дру­гу, за исклю­чени­ем того, что Invoke-RestMethod авто­мати­чес­ки пре­обра­зует ответ от сер­вера в объ­ект PowerShell.

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei



Report Page