前回から、Webページを自動取得するPowerShellスクリプトの作成に取り掛かっている。前回は、Windowsに同梱されているcurl.exeを使ってWebページを取得する方法を試し、これをベースにPowerShellスクリプトを作成した。今回はこれをさらに改良していく。

Windowsのcurl.exeを使う

curlコマンドはもともとLinuxなどのUNIX系OSで活用されているソフトウエアだ。シンプルにして強力なので、Webリソースの取得のみならず、Webサーバとの対話など多くの操作を行うことができる万能ツールとして重宝されている。

そして最近、Windowsにもデフォルトでこのコマンドが同梱されるようになった。現在では「C:\Windows\System32\curl.exe」にデプロイされているはずだ。

  • C:\Windows\System32\curl.exeにデフォルトでデプロイされるようになったcurlコマンド

    C:\Windows\System32\curl.exeにデフォルトでデプロイされるようになったcurlコマンド

前回は、このcurl.exeを実行する簡単なPowerShellスクリプトを作成した。次の「netcat.ps1」だ。

#!/usr/bin/env pwsh

#========================================================================
# URLで指定されたリソースを取得
#========================================================================

#========================================================================
# 引数を処理
#   -URL url        WebリソースのURL
#========================================================================
Param(
    [Parameter(Mandatory=$true)][String]$URL = ""
)

#========================================================================
# Webリソース取得に利用するアプリケーション
#========================================================================
$curl='C:\Windows\System32\curl.exe'

#========================================================================
# curl を使って取得する
#========================================================================
& $curl -get $URL

しかし、実はこのスクリプトでは取得できないWebページというものがいくつも存在している。curlにはそうした場合に使用できるオプションが用意されており、ある程度オプションを組み合わせることでより多くのWebページが取得できるようになる。今回はそうした部分に焦点を当てて改良していく。

curlで取得できないWebページを探す

以前「mail.ps1」を作成したときもそうだったが、ここから先は試行錯誤の段階だ。自動取得したいと考えているWebページがきちんと取得できるか実験し、取得できなければ原因を探し、動作するようにcurlにオプションを指定する。ひたすらこれを繰り返し、なるべくどのWebページも取得できるオプションを探していく作業になる。

本稿では結果だけを掲載していくが、可能な範囲で自分でもいろいろ試してもらえればと思う。

まず、次のようなケースだ。curlでWebページを取得しようとすると「301 Moved Permanently」というページが返ってきている。

PS C:\Users\daichi> C:\Windows\System32\curl.exe -get http://pypl.github.io/DB.html
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
PS C:\Users\daichi>

これは、結構よくあるケースだ。答えを書いておくと、この場合には「--location」を指定すればよい。この指定を行うと転送先からWebリソースを取得してくれるので、問題なくWebページが入手できるようになる。

「--location」を指定して先ほどのWebページを取得しようとすると、次のようになる。

PS C:\Users\daichi> C:\Windows\System32\curl.exe --location -get http://pypl.github.io/DB.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>TOPDB Top Database index</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

...略...

</-->
  </body>
</html>
PS C:\Users\daichi>

ちゃんとWebページの内容が取得できていることがわかる。

なお、Webページの内容が取得できているかどうかは、実際にWebブラウザで表示させながら、その内容と比較するとよい。異なる内容が表示されていれば、curlの方で適切にWebリソースが取得できていない可能性が高い。

今度は別のケースを見てみよう。こちらでは「error code: 1020」が返ってきている。

PS C:\Users\daichi> C:\Windows\System32\curl.exe --location -get https://blog.talosintelligence.com/2022/05/mustang-panda-targets-europe.html
error code: 1020
PS C:\Users\daichi>

いくつか原因が考えられるが、この場合にはクライアント(この場合はcurlということになる)が素性を明かしていないのがネックになっている。Webサーバによっては、相手が不明瞭な場合にはコンテンツへのアクセスを拒否するものがあり、今回はそれに該当しているのだ。

これは、Webブラウザが送っているのと同じエージェント名を名乗るようにすると解決することがある。例えば、ここではシンプルに「Mozilla/5.0」というエージェント名を名乗るとしよう。これは「-A」で指定するので、「-A Mozilla/5.0」を追加することになる。

実行すると次のようになる。

PS C:\Users\daichi> C:\Windows\System32\curl.exe --location -A Mozilla/5.0 -get https://blog.talosintelligence.com/2022/05/mustang-panda-targets-europe.html
<!DOCTYPE html>

...略...

<head>
<title>Attention Required! | Cloudflare</title>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />

...略...

</script>

</body>
</html>
PS C:\Users\daichi>

今度はWebページが取得できたことがわかる。

似たようなケースなのだが、次のように「Not Acceptable」となったケースも見てみよう。

PS C:\Users\daichi> C:\Windows\System32\curl.exe --location -A Mozilla/5.0 -get https://decoded.avast.io/davidalvarez/linux-threat-hunting-syslogk-a-kernel-rootkit-found-under-development-in-the-wild/
<head><title>Not Acceptable!</title></head><body><h1>Not Acceptable!</h1><p>An appropriate representation of the requested resource could not be found on this server. This error was generated by Mod_Security.</p></body></html>
PS C:\Users\daichi>

実は、これも先ほどと同じ理由で拒否されている。「-A Mozilla/5.0」は指定してあるのだが、それだけでは情報が足りないというわけだ。

これはもう少し詳しくエージェントの情報を渡せば解決する。例えば「Mozilla/5.0 (Windows NT 10.0; Win64; x64)」といったエージェント名を指定するといった具合だ。実行すると次のようになる。

C:\Windows\System32\curl.exe --location -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" -get https://decoded.avast.io/davidalvarez/linux-threat-hunting-syslogk-a-kernel-rootkit-found-under-development-in-the-wild/
<html lang="en-US" class="no-js no-svg">

        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <link rel="profile" href="https://gmpg.org/xfn/11" />
            <meta name='robots' content='index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1' />

...略...


    const separators = document.querySelectorAll('div.nsl-separator');
    if (hasWebViewLimitation && separators.length) {
        separators.forEach(function (separator) {
            let separatorParentNode = separator.parentNode;
            if (separatorParentNode) {
                const separatorButtonContainer = separatorParentNode.querySelector('div.nsl-container-buttons');
                if (separatorButtonContainer && !separatorButtonContainer.hasChildNodes()) {
                    separator.remove();
                }
            }
        })
    }
});})();</script></body>

</html>
PS C:\Users\daichi>

今度はちゃんとWebページが取得できていることがわかる。

Webページによっては、相手が人間ではなく悪意あるプログラムである場合を考慮して、アクセスに条件や制限を設けているところがある。そういったコンテンツは、デフォルトのcurlコマンドのままでは取得できないのだ。その場合は、上述したようにオプションを組み合わせて実行する必要がある。