第 4 章 関数の使用法
4.1 Java API の操作
Elasticsearch 8.x の新バージョンの登場により、Type の概念は廃止されました。このデータ構造の変更に適応するために、Elasticsearch 関係者は、新しい
Elasticsearch Java クライアントを使用することを推奨しています。バージョン7.15から。
4.1.1 依存関係の追加
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<elastic.version>8.1.0</elastic.version>
</properties>
<dependencies>
<dependency>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>x-pack-sql-jdbc</artifactId>
<version>8.1.0</version>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>${
elastic.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
4.1.2 クライアント オブジェクトの取得
MySQL データベースに接続するのと同じように、Java は接続を取得した後でのみクライアント経由で Elasticsearch を操作できます
。現在、https ベースの安全な Elasticsearch サービスを使用しているため、最初に以前の証明書をopenssl pkcs12 -in elastic-stack-ca.p12 -clcerts -nokeys -out java-ca.crtに
変換する必要があります。証明書を構成した後、 https を使用して接続オブジェクトを取得できます。
# 导入的类
import co.elastic.clients.elasticsearch.*;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.*;
import org.apache.http.client.*;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.*;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.ssl.*;
import org.elasticsearch.client.*;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.nio.file.*;
import java.security.KeyStore;
import java.security.cert.*;
# 获取客户端对象
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials("elastic", "O3x0hfu7i=ZbQvlktCnd"));
Path caCertificatePath = Paths.get("ca.crt");
CertificateFactory factory =
CertificateFactory.getInstance("X.509");
Certificate trustedCa;
try (InputStream is = Files.newInputStream(caCertificatePath)) {
trustedCa = factory.generateCertificate(is);
}
KeyStore trustStore = KeyStore.getInstance("pkcs12");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", trustedCa);
SSLContextBuilder sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(trustStore, null);
final SSLContext sslContext = sslContextBuilder.build();
RestClientBuilder builder = RestClient.builder(
new HttpHost("linux1", 9200, "https"))
.setHttpClientConfigCallback(new
RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setDefaultCredentialsProvider(credentialsProvider);
}
});
RestClient restClient = builder.build();
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
ElasticsearchClient client = new ElasticsearchClient(transport);
ElasticsearchAsyncClient asyncClient = new ElasticsearchAsyncClient(transport);
...
transport.close();
4.1.3 運転データ(通常運転)
4.1.3.1 インデックス運転
// 创建索引
CreateIndexRequest request = new
CreateIndexRequest.Builder().index("myindex").build();
final CreateIndexResponse createIndexResponse =
client.indices().create(request);
System.out.println("创建索引成功:" + createIndexResponse.acknowledged());
// 查询索引
GetIndexRequest getIndexRequest = new
GetIndexRequest.Builder().index("myindex").build();
final GetIndexResponse getIndexResponse =
client.indices().get(getIndexRequest);
System.out.println("索引查询成功:" + getIndexResponse.result());
// 删除索引
DeleteIndexRequest deleteIndexRequest = new
DeleteIndexRequest.Builder().index("myindex").build();
final DeleteIndexResponse delete = client.indices().delete(deleteIndexRequest);
final boolean acknowledged = delete.acknowledged();
System.out.println("删除索引成功:" + acknowledged)
4.1.3.2 文書操作
// 创建文档
IndexRequest indexRequest = new IndexRequest.Builder()
.index("myindex")
.id(user.getId().toString())
.document(user)
.build();
final IndexResponse index = client.index(indexRequest);
System.out.println("文档操作结果:" + index.result());
// 批量创建文档
final List<BulkOperation> operations = new ArrayList<BulkOperation>();
for ( int i= 1;i <= 5; i++ ) {
final CreateOperation.Builder builder = new CreateOperation.Builder();
builder.index("myindex");
builder.id("200" + i);
builder.document(new User(2000 + i, 30 + i * 10, "zhangsan" + i, "beijing",
1000 + i*1000));
final CreateOperation<Object> objectCreateOperation = builder.build();
final BulkOperation bulk = new
BulkOperation.Builder().create(objectCreateOperation).build();
operations.add(bulk);
}
BulkRequest bulkRequest = new
BulkRequest.Builder().operations(operations).build();
final BulkResponse bulkResponse = client.bulk(bulkRequest);
System.out.println("数据操作成功:" + bulkResponse);
// 删除文档
DeleteRequest deleteRequest = new
DeleteRequest.Builder().index("myindex").id("1001").build();
client.delete(deleteRequest);
4.1.3.3 ドキュメントクエリ
final SearchRequest.Builder searchRequestBuilder = new
SearchRequest.Builder().index("myindex1");
MatchQuery matchQuery = new
MatchQuery.Builder().field("city").query(FieldValue.of("beijing")).build();
Query query = new Query.Builder().match(matchQuery).build();
searchRequestBuilder.query(query);
SearchRequest searchRequest = searchRequestBuilder.build();
final SearchResponse<Object> search = client.search(searchRequest,
Object.class);
System.out.println(search);
4.1.4 演算データ(関数演算)
4.1.4.1 インデックス演算
// 创建索引
final Boolean acknowledged = client.indices().create(p ->
p.index("")).acknowledged();
System.out.println("创建索引成功");
// 获取索引
System.out.println(
client.indices().get(
req -> req.index("myindex1")
).result());
// 删除索引
client.indices().delete(
reqbuilder -> reqbuilder.index("myindex")
).acknowledged();
4.1.4.2 文書操作
// 创建文档
System.out.println(
client.index(
req ->
req.index("myindex")
.id(user.getId().toString())
.document(user)
).result()
);
// 批量创建文档
client.bulk(
req -> {
users.forEach(
u -> {
req.operations(
b -> {
b.create(
d ->
d.id(u.getId().toString()).index("myindex").document(u)
);
return b;
}
);
}
);
return req;
}
);
// 删除文档
client.delete(
req -> req.index("myindex").id("1001")
);
4.1.4.3 ドキュメントクエリ
client.search(
req -> {
req.query(
q ->
q.match(
m -> m.field("city").query("beijing")
)
);
return req;
}
, Object.class
);
4.1.5 クライアント側の非同期操作
ES Java API は、クライアント側の処理として同期と非同期の 2 種類を提供します。これまでのデモはすべて同期処理でしたが、
非同期クライアント処理と同期クライアント処理の基本原理は同じですが、異なる点は返された結果を
非同期で処理する必要があることです。
// 创建索引
asyncClient.indices().create(
req -> {
req.index("newindex");
return req;
}
).whenComplete(
(resp, error) -> {
System.out.println("回调函数");
if ( resp != null ) {
System.out.println(resp.acknowledged());
} else {
error.printStackTrace();
}
}
);
System.out.println("主线程操作...");
asyncClient.indices().create(
req -> {
req.index("newindex");
return req;
}
)
.thenApply(
resp -> {
return resp.acknowledged();
}
)
.whenComplete(
(resp, error) -> {
System.out.println("回调函数");
if ( !resp ) {
System.out.println();
} else {
error.printStackTrace();
}
}
);
4.2 EQL の操作
EQL の正式名は、Event Query Language (EQL) です。イベント クエリ言語 (EQL) は、
ログ、メトリック、トレースなどのイベントベースの時系列データ用のクエリ言語です。Elastic Security プラットフォームでは、
有効な EQL が入力されると、クエリがデータ ノード上でコンパイルされ、クエリが実行され、結果が返されます。これらはすべて
迅速かつ並行して行われるため、ユーザーは結果をすぐに確認できます。
EQL の利点:
➢ EQL を使用すると、イベント間の関係を表現でき、
多くのクエリ言語で個々のイベントを照合できます。
EQL を使用すると、さまざまなイベント カテゴリや期間にわたって一連のイベントを照合できます。
➢ EQL は学習曲線が低く、
EQL 構文は SQL などの他の一般的なクエリ言語と似ています。EQL を使用すると、クエリを直感的に作成および読み取りできる
ため、高速で反復的な検索が可能になります。
➢ セキュリティのユースケース向けに設計された EQL
あらゆるイベントベースのデータで使用できますが、私たちは脅威ハンティングのために EQL を作成しました。EQL は、
IOC (Indicators of Compromise) 検索をサポートするだけでなく、IOC の範囲を超えたアクティビティを記述することもできます。
4.2.1 基本構文
4.2.1.1 データの準備
EQL 検索を実行するには、検索されるデータ ストリームまたはインデックスにタイムスタンプ フィールドとイベント カテゴリ フィールドが含まれている必要があります。デフォルトでは、
EQL は Elastic Common Schema (ECS) の @timestamp フィールドとevent.category フィールドを使用します。
@timestamp はタイムスタンプを表し、event.category はイベント カテゴリを表します。
電子商取引 Web サイトのページジャンプを表す簡単なデータを準備しましょう
# 创建索引
PUT /gmall
# 批量增加数据
PUT _bulk
{
"index":{
"_index":"gmall"}}
{
"@timestamp":"2022-06-01T12:00:00.00+08:00",
"event":{
"category":"page"},"page" : {
"session_id" :
"42FC7E13-CB3E-5C05-0000-0010A0125101","last_page_id" : "","page_id" :
"login","user_id" : ""}}
{
"index":{
"_index":"gmall"}}
{
"@timestamp":"2022-06-01T12:01:00.00+08:00",
"event":{
"category":"page"},"page" : {
"session_id" :
"42FC7E13-CB3E-5C05-0000-0010A0125101","last_page_id" : "login","page_id" :
"good_list","user_id" : "1"}}
{
"index":{
"_index":"gmall"}}
{
"@timestamp":"2022-06-01T12:05:00.00+08:00",
"event":{
"category":"page"},"page" : {
"session_id" :
"42FC7E13-CB3E-5C05-0000-0010A0125101","last_page_id" : "good_list","page_id" :
"good_detail","user_id" : "1"}}
{
"index":{
"_index":"gmall"}}
{
"@timestamp":"2022-06-01T12:07:00.00+08:00",
"event":{
"category":"page"},"page" : {
"session_id" :
"42FC7E13-CB3E-5C05-0000-0010A0125101","last_page_id" :
"good_detail","page_id" : "order","user_id" : "1"}}
{
"index":{
"_index":"gmall"}}
{
"@timestamp":"2022-06-01T12:08:00.00+08:00",
"event":{
"category":"page"},"page" : {
"session_id" :
"42FC7E13-CB3E-5C05-0000-0010A0125101","last_page_id" : "order","page_id" :
"payment","user_id" : "1"}}
{
"index":{
"_index":"gmall"}}
{
"@timestamp":"2022-06-01T12:08:00.00+08:00",
"event":{
"category":"page"},"page" : {
"session_id" :
"42FC7E13-CB3E-5C05-0000-0010A0125102","last_page_id" : "","page_id" :
"login","user_id" : "2"}}
{
"index":{
"_index":"gmall"}}
{
"@timestamp":"2022-06-01T12:08:00.00+08:00",
"event":{
"category":"page"},"page" : {
"session_id" :
"42FC7E13-CB3E-5C05-0000-0010A0125102","last_page_id" : "login","page_id" :
"payment","user_id" : "2"}}
4.2.1.2 データウィンドウ検索
インシデント対応プロセス中、特定の時間に発生したすべてのイベントを知っておくと役立つことがよくあります。すべてのイベントと一致するには、any という名前の特別なイベント タイプを使用します。特定のイベントと一致する場合は、イベント分類名
を指定する必要があります。
#
GET /gmall/_eql/search
{
"query" : """
any where page.user_id == "1"
"""
}
4.2.1.3 予選イベントの統計
#
GET /gmall/_eql/search
{
"query" : """
any where true
""",
"filter": {
"range": {
"@timestamp": {
"gte": "1654056000000",
"lt": "1654056005000"
}
}
}
}
4.2.1.4 イベントシーケンス
# 页面先访问 login,后面又访问了 good_detail 的页面
GET /gmall/_eql/search
{
"query" : """
sequence by page.session_id
[page where page.page_id=="login"]
[page where page.page_id=="good_detail"]
"""
}
4.2.2 セキュリティ検出
EQL は Elastic Securit で広く使用されています。実際のアプリケーションでは、EQL 言語を使用して
セキュリティの脅威やその他の不審な動作を検出できます。
4.2.2.1 データの準備
regsvr32.exe は、Windows に .dll ライブラリを登録するための組み込みコマンド ライン ユーティリティです。regsvr32.exe はネイティブ
ツールとして信頼できるステータスを持ち、ほとんどの許可リスト ソフトウェアやスクリプト ブロッカーをバイパスできます
。
ユーザーのコマンド ラインにアクセスできる攻撃者は、regsvr32.exe を使用して、スクリプトの実行が許可されていない場合でも、.dll ライブラリを通じて悪意のあるスクリプトを実行する可能性があります。
regsvr32 悪用の一般的な亜種は、Squfuldoo 攻撃です。Squfuldoo 攻撃では、regsvr32.exe
コマンドが scrobj.dll ライブラリを使用してリモート スクリプトを登録し、実行します。
テスト データは、Atomic Red Team のテスト データ セットから取得したもので、Squibledoo 攻撃を模倣したイベントが含まれています。
データは Elastic Common Schema (ECS) フィールドにマッピングされています:normalized-T1117-AtomicRed-regsvr32.json
ファイルの内容を ES ソフトウェアにインポートします。
# 创建索引
PUT my-eql-index
# 导入数据
POST my-eql-index/_bulk?pretty&refresh
データのインポート状況を確認する
# 导入数据
GET /_cat/indices/my-eql-index?v=true&h=health,status,index,docs.count
4.2.2 regsvr32 イベントの数を取得する
regsvr32.exe プロセスに関連付けられたイベントの数を取得する
# 查询数据
# ?filter_path=-hits.events 从响应中排除 hits.events 属性。 此搜索仅用于获取事件计数,
而不是匹配事件的列表
# query : 匹配任何进程名称为 regsvr32.exe 的事件
# size : 最多返回 200 个匹配事件的匹配,实际查询结果为 143 个
GET my-eql-index/_eql/search?filter_path=-hits.events
{
"query": """
any where process.name == "regsvr32.exe"
""",
"size": 200
}
4.2.3 コマンド ライン パラメータの確認
regsvr32.exe プロセスは 143 個のイベントに関連付けられています。しかし、そもそも regsvr32.exe を呼び出すにはどうすればよいでしょうか? 誰がそう呼んだの?
regsvr32.exe はコマンド ライン ユーティリティです。コマンドラインを使用して結果をプロセスに絞り込む
# 增加过滤条件查询数据
GET my-eql-index/_eql/search
{
"query": """
process where process.name == "regsvr32.exe" and
process.command_line.keyword != null
"""
}
このクエリは、作成されたevent.typeとイベントを照合し、regsvr32.exeプロセスの開始を示します。
イベントの process.command_line 値によると、regsvr32.exe は scrobj.dll を使用してスクリプト RegSvr32.sct を登録します。これは
Squibledoo 攻撃の動作と一致しています
4.2.4 悪意のあるスクリプトの読み込みを確認する
regsvr32.exe が読み込まれるかどうかを確認する将来の scrobj.dll ライブラリ
# 增加过滤条件查询数据
GET my-eql-index/_eql/search
{
"query": """
library where process.name == "regsvr32.exe" and dll.name == "scrobj.dll"
"""
}
4.2.5 攻撃が成功する可能性の確認
多くの場合、攻撃者は悪意のあるスクリプトを使用してリモート サーバーに接続したり、追加のファイルをダウンロードしたりします。EQL シーケンス
クエリを使用して、次の一連のイベントを調べます。
➢ regsvr32.exe プロセス
➢ 同じプロセスを介した scrobj.dll ライブラリのロード
➢ 同じプロセス内のネットワーク イベント
前に示したコマンド ライン値に基づきます。応答があれば、一致が見つかったことが期待できます。ただし、このクエリは
この特定のコマンド用に設計されたものではありません。
代わりに、同様の脅威を検出するのに十分な不審な動作のパターンを探します。
# 增加过滤条件查询数据
GET my-eql-index/_eql/search
{
"query": """
sequence by process.pid
[process where process.name == "regsvr32.exe"]
[library where dll.name == "scrobj.dll"]
[network where true]
"""
}
4.3 SQL操作
通常、Elasticsearch を使用する場合、データのクエリには Query DSL が使用されますが、Elasticsearch6.3 バージョン以降
、Elasticsearch はすでに SQL クエリをサポートしています。Elasticsearch SQL は、Elasticsearch に対して
SQL のようなクエリをリアルタイムで実行できる X-Pack コンポーネントです。
REST インターフェイス、コマンドライン、または JDBC のいずれを使用している場合でも、クライアントは SQL を使用して、
Elasticsearch でデータをネイティブに検索および集約できます。Elasticsearch SQL は、SQL をクエリ DSL に変換するトランスレーターと考えてください
。
Elasticsearch SQL には次の機能があります。
➢ ネイティブ サポート: Elasticsearch SQL は Elasticsearch 用に特別に構築されています。
➢ 追加部品なし: Elasticsearch のクエリに他のハードウェア、プロセッサ、オペレーティング環境、依存ライブラリは必要なく、
Elasticsearch SQL は Elasticsearch 内で直接実行されます。
➢ 軽量かつ効率的: Elasticsearch SQL は検索機能を抽象化せず、逆に SQL を受け入れて
全文検索を実装し、簡潔な方法でリアルタイムに全文検索を実行します。
4.3.1 SQL と Elasticsearch の対応
SQL と Elasticsearch では、データの編成方法 (およびセマンティクス) に関する用語が異なりますが、目的は
本質的に同じです。
概念間のマッピングは完全に 1 対 1 ではなく、セマンティクスは異なりますが、相違点よりも類似点の方が多くなります。実際、
SQL の多くの概念は Elasticsearch で対応関係を見つけることができ、この 2 つの用語も非常によく似ています
。
# 创建索引并增加数据,等同于创建表和数据
PUT my-sql-index/_bulk?refresh
{
"index":{
"_id": "JAVA"}}
{
"name": "JAVA", "author": "zhangsan", "release_date": "2022-05-01",
"page_count": 561}
{
"index":{
"_id": "BIGDATA"}}
{
"name": "BIGDATA", "author": "lisi", "release_date": "2022-05-02", "page_count":
482}
{
"index":{
"_id": "SCALA"}}
{
"name": "SCALA", "author": "wangwu", "release_date": "2022-05-03", "page_count":
604}
4.3.3 第一个 SQL 查询
现在可以使用 SQL 对数据进行查询了。
# SQL
# 这里的表就是索引
# 可以通过 format 参数控制返回结果的格式,默认为 json 格式
# txt:表示文本格式,看起来更直观点.
# csv:使用逗号隔开的数据
# json:JSON 格式数据
# tsv: 使用 tab 键隔开数据
# yaml:属性配置格式
POST _sql?format=txt
{
"query": """
SELECT * FROM "my-sql-index"
"""
}
# 条件查询
POST _sql?format=txt
{
"query": """
SELECT * FROM "my-sql-index" where page_count > 500
"""
}
実際、JDBC 操作の SQL 構文は基本的に同じであることがわかります。
4.3.3 SQL から DSL への変換
Query DSL を使用する必要がある場合、最初に SQL を使用してクエリを実行し、次に Translate API を介して変換することもできます
。クエリ結果は DSL 結果です。
# 转换 SQL 为 DSL 进行操作
POST _sql/translate
{
"query": """
SELECT * FROM "my-sql-index" where page_count > 500
"""
}
4.3.4 SQL と DSL の混合使用
SQL ステートメントを最適化した後でもクエリ要件を満たせない場合は、SQL と DSL を混合できます。ES は最初に
SQL に基づいてクエリを実行し、次に SQL 実行結果に基づいて二次クエリを実行します。 DSL ステートメントについて。
# SQL 和 DSL 混合使用
# 由于索引中含有横线,所以作为表名时需要采用双引号,且外层需要三个引号包含
POST _sql?format=txt
{
"query": """SELECT * FROM "my-sql-index" """,
"filter" : {
"range": {
"page_count": {
"gte": 400,
"lte": 600
}
}
},
"fetch_size": 2
}