A Internet das Coisas (IoT) transformou profundamente a forma como sistemas conectados operam em ambientes domésticos, comerciais e especialmente industriais. Quando esses dispositivos são aplicados ao contexto fabril, nasce o conceito de Industrial Internet of Things (IIoT) — uma rede de sensores, atuadores, controladores lógicos, máquinas inteligentes e servidores conectados com o objetivo de automatizar processos, aumentar produtividade e reduzir custos operacionais.
Embora essa integração digital tenha potencializado a eficiência industrial, ela também ampliou a superfície de ataque dessas redes. Estimativas apontam que mais de 75% dos dispositivos industriais conectados possuem vulnerabilidades conhecidas e que o número de ataques contra sistemas de controle industrial (ICS) aumentou mais de 300% nos últimos anos (ENISA, 2020 ) e (IBM X-Force, 2022 ). Incidentes como o ataque ao oleoduto Colonial Pipeline (2021), o caso do ransomware LockerGoga (2019) e invasões a usinas de água e energia mostram como falhas de segurança em sistemas industriais podem gerar prejuízos financeiros, ambientais e até riscos à vida humana.
Diferentemente de redes convencionais, ambientes IIoT possuem características muito específicas: baixa tolerância a interrupções, necessidade de comunicação em tempo real, uso de protocolos proprietários e dispositivos com capacidade limitada de processamento. Além disso, ataques em redes industriais tendem a ser raros, difíceis de observar e com comportamento muito distinto do tráfego normal. Isso torna essencial o desenvolvimento de métodos robustos de detecção de anomalias baseados em dados reais que representem adequadamente a dinâmica de um ambiente industrial.
Nesse contexto, este trabalho utiliza o dataset público WUSTL-IIoT-2021, desenvolvido pela Washington University in St. Louis, que contém mais de 1,1 milhão de registros de tráfego realista de um testbed industrial, incluindo comunicações normais e diversos ataques como DoS, Reconnaissance, Command Injection e Backdoor. O dataset está disponível em: 🔗 http://www.cse.wustl.edu/~jain/iiot2/index.html
O conjunto de dados foi construído deliberadamente de forma desbalanceada, refletindo o cenário real das indústrias, onde ataques representam menos de 8% do tráfego total (Zolanvari et al., 2021 ).
O objetivo deste estudo é realizar uma análise exploratória detalhada desse dataset a fim de compreender padrões presentes no tráfego industrial, identificar diferenças entre fluxos benignos e maliciosos e discutir como essas informações podem apoiar modelos futuros de detecção de intrusão. Ao investigar métricas como volume de pacotes, taxas de transmissão, jitter, perda de pacotes e características de tempo de conexão, buscamos revelar comportamentos anômalos que podem sinalizar tentativas de ataque.
Assim, esta análise contribui para:
compreensão profunda da estrutura e padrões do tráfego industrial;
identificação de sinais característicos de ataques raros, essenciais para sistemas de detecção de intrusões (IDS);
reforço da importância da segurança em ambientes industriais conectados, tema cada vez mais relevante para infraestrutura crítica;
oferta de insights práticos para pesquisadores e profissionais, que podem aplicar as descobertas em soluções reais de proteção cibernética.
Dessa forma, o estudo da segurança de dados em IIoT não é apenas uma demanda técnica: é uma necessidade urgente para proteger sistemas que sustentam energia, água, transporte, saúde e produção industrial moderna.
# Importar pacotes
library(readr)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ purrr 1.1.0
## ✔ forcats 1.0.1 ✔ stringr 1.5.2
## ✔ ggplot2 4.0.0 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(lubridate)
O dataset WUSTL-IIoT-2021 contém registros de tráfego de rede coletados em um testbed industrial realista, com comunicações normais e ataques cibernéticos reais (DoS, Reconnaissance, Command Injection e Backdoor). Ele representa o comportamento típico de um ambiente IIoT moderno, onde a maioria do tráfego é normal, mas ataques esporádicos representam grande risco.
Nosso objetivo nesta etapa é compreender a estrutura dos dados, observando:
Isso é fundamental antes de qualquer análise mais profunda.
# Carregar dataset
data <- read_csv("wustl_iiot_2021.csv")
## Rows: 1194464 Columns: 49
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (3): SrcAddr, DstAddr, Traffic
## dbl (44): Mean, Sport, Dport, SrcPkts, DstPkts, TotPkts, DstBytes, SrcBytes...
## dttm (2): StartTime, LastTime
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
View(data)
# Número de linhas e colunas
print(paste("O número de linhas é:", nrow(data)))
## [1] "O número de linhas é: 1194464"
print(paste("O número de colunas é:", ncol(data)))
## [1] "O número de colunas é: 49"
O dataset tem quase quase 1,2 milhão de registros e 49 colunas, então é um dataset consideravelmente grande de onde podemos tirar varias informações. Nos temos as colunas :
str(data)
## spc_tbl_ [1,194,464 × 49] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ StartTime : POSIXct[1:1194464], format: "2019-08-19 12:23:28" "2019-08-19 15:13:24" ...
## $ LastTime : POSIXct[1:1194464], format: "2019-08-19 12:23:28" "2019-08-19 15:13:24" ...
## $ SrcAddr : chr [1:1194464] "192.168.0.20" "192.168.0.20" "192.168.0.20" "209.240.235.92" ...
## $ DstAddr : chr [1:1194464] "192.168.0.2" "192.168.0.2" "192.168.0.2" "192.168.0.2" ...
## $ Mean : num [1:1194464] 0 0 0 0 3 0 0 0 0 0 ...
## $ Sport : num [1:1194464] 59034 55841 63774 61771 0 ...
## $ Dport : num [1:1194464] 502 502 502 80 0 80 502 80 502 502 ...
## $ SrcPkts : num [1:1194464] 10 10 10 4 14 2 6 2 10 10 ...
## $ DstPkts : num [1:1194464] 8 8 8 0 0 0 6 0 8 8 ...
## $ TotPkts : num [1:1194464] 18 18 18 4 14 2 12 2 18 18 ...
## $ DstBytes : num [1:1194464] 508 508 508 0 0 0 384 0 508 508 ...
## $ SrcBytes : num [1:1194464] 644 644 644 248 868 124 396 124 644 644 ...
## $ TotBytes : num [1:1194464] 1152 1152 1152 248 868 ...
## $ SrcLoad : num [1:1194464] 87486 88077 89587 1673 1842 ...
## $ DstLoad : num [1:1194464] 67123 67577 68735 0 0 ...
## $ Load : num [1:1194464] 154609 155654 158323 1673 1842 ...
## $ SrcRate : num [1:1194464] 169.69 170.84 173.77 3.37 3.71 ...
## $ DstRate : num [1:1194464] 132 133 135 0 0 ...
## $ Rate : num [1:1194464] 320.53 322.7 328.23 3.37 3.71 ...
## $ SrcLoss : num [1:1194464] 2 2 2 3 0 2 2 2 2 2 ...
## $ DstLoss : num [1:1194464] 2 2 2 0 0 0 2 0 2 2 ...
## $ Loss : num [1:1194464] 4 4 4 3 0 2 4 2 4 4 ...
## $ pLoss : num [1:1194464] 18.2 18.2 18.2 42.9 0 ...
## $ SrcJitter : num [1:1194464] 527.4 17.2 523 419.3 525.1 ...
## $ DstJitter : num [1:1194464] 11.5 13.2 12.3 0 0 ...
## $ SIntPkt : num [1:1194464] 5.89 7.53 5.75 296.52 321.43 ...
## $ DIntPkt : num [1:1194464] 7.41 7.34 7.3 0 0 ...
## $ Proto : num [1:1194464] 6 6 6 6 2054 ...
## $ Dur : num [1:1194464] 0.053 0.0527 0.0518 0.8896 3.5001 ...
## $ TcpRtt : num [1:1194464] 0.001266 0.00131 0.000766 0 0 ...
## $ IdleTime : num [1:1194464] 1.55e+09 1.55e+09 1.55e+09 1.55e+09 1.55e+09 ...
## $ Sum : num [1:1194464] 0.053 0.0527 0.0518 0.8896 3.5001 ...
## $ Min : num [1:1194464] 0.053 0.0527 0.0518 0.8896 3.5001 ...
## $ Max : num [1:1194464] 0.053 0.0527 0.0518 0.8896 3.5001 ...
## $ sDSb : num [1:1194464] 0 0 0 0 0 0 0 0 0 0 ...
## $ sTtl : num [1:1194464] 128 128 128 140 0 142 128 243 128 128 ...
## $ dTtl : num [1:1194464] 64 64 64 0 0 0 64 0 64 64 ...
## $ sIpId : num [1:1194464] 53331 37167 58712 21629 0 ...
## $ dIpId : num [1:1194464] 64402 31590 22717 0 0 ...
## $ SAppBytes : num [1:1194464] 24 24 24 0 476 0 24 0 24 24 ...
## $ DAppBytes : num [1:1194464] 20 20 20 0 0 0 20 0 20 20 ...
## $ TotAppByte: num [1:1194464] 44 44 44 0 476 0 44 0 44 44 ...
## $ SynAck : num [1:1194464] 0.00118 0.00131 0.00069 0 0 ...
## $ RunTime : num [1:1194464] 0.053 0.0527 0.0518 0.8896 3.5001 ...
## $ sTos : num [1:1194464] 0 0 0 0 0 0 0 0 0 0 ...
## $ SrcJitAct : num [1:1194464] 0 0 0 419 525 ...
## $ DstJitAct : num [1:1194464] 0 0 0 0 0 0 0 0 0 0 ...
## $ Traffic : chr [1:1194464] "normal" "normal" "normal" "DoS" ...
## $ Target : num [1:1194464] 0 0 0 1 0 1 0 1 0 0 ...
## - attr(*, "spec")=
## .. cols(
## .. StartTime = col_datetime(format = ""),
## .. LastTime = col_datetime(format = ""),
## .. SrcAddr = col_character(),
## .. DstAddr = col_character(),
## .. Mean = col_double(),
## .. Sport = col_double(),
## .. Dport = col_double(),
## .. SrcPkts = col_double(),
## .. DstPkts = col_double(),
## .. TotPkts = col_double(),
## .. DstBytes = col_double(),
## .. SrcBytes = col_double(),
## .. TotBytes = col_double(),
## .. SrcLoad = col_double(),
## .. DstLoad = col_double(),
## .. Load = col_double(),
## .. SrcRate = col_double(),
## .. DstRate = col_double(),
## .. Rate = col_double(),
## .. SrcLoss = col_double(),
## .. DstLoss = col_double(),
## .. Loss = col_double(),
## .. pLoss = col_double(),
## .. SrcJitter = col_double(),
## .. DstJitter = col_double(),
## .. SIntPkt = col_double(),
## .. DIntPkt = col_double(),
## .. Proto = col_double(),
## .. Dur = col_double(),
## .. TcpRtt = col_double(),
## .. IdleTime = col_double(),
## .. Sum = col_double(),
## .. Min = col_double(),
## .. Max = col_double(),
## .. sDSb = col_double(),
## .. sTtl = col_double(),
## .. dTtl = col_double(),
## .. sIpId = col_double(),
## .. dIpId = col_double(),
## .. SAppBytes = col_double(),
## .. DAppBytes = col_double(),
## .. TotAppByte = col_double(),
## .. SynAck = col_double(),
## .. RunTime = col_double(),
## .. sTos = col_double(),
## .. SrcJitAct = col_double(),
## .. DstJitAct = col_double(),
## .. Traffic = col_character(),
## .. Target = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
Antes de iniciar a análise exploratória, foram realizadas verificações básicas de integridade do dataset, incluindo a presença de valores ausentes e a existência de registros duplicados. O código abaixo foi utilizado para essa inspeção inicial:
sum(is.na(data))
## [1] 0
key_cols <- c("SrcAddr", "DstAddr", "Sport", "Dport", "StartTime")
sum(duplicated(data[key_cols]))
## [1] 93
data <- data[!duplicated(data[key_cols]), ]
Os resultados mostraram que não há valores ausentes no dataset, confirmando que os dados já vêm limpos de origem. Quanto à duplicação, ao realizar a checagem utilizando todas as colunas, o processamento se tornou extremamente lento devido ao tamanho do dataset (mais de 1,19 milhão de registros). Por isso, optou-se por verificar duplicatas apenas nas colunas-chave do fluxo, como IPs, portas e timestamp inicial. Essa abordagem é adequada para análise exploratória e reduz significativamente o custo computacional.
A partir dessa verificação, foram encontrados 93 registros duplicados entre as colunas-chave, o que representa uma proporção mínima diante do volume total do dataset. Assim, conclui-se que não há duplicações relevantes que comprometam a análise exploratória dos dados, porém mesmo assim preferi remove-los.
data <- data %>%
mutate(
Traffic = as.factor(Traffic),
Target = as.factor(Target)
)
Traffic (normal, DoS, Recon, etc.) e Target (0/1) são categorias, não número, então transformá-las em fatores permite análises mais corretas, como gráficos comparativos e estatísticas por grupo
data <- data %>%
mutate(
hour = hour(StartTime),
duration = as.numeric(LastTime - StartTime),
byte_ratio = SrcBytes / (DstBytes + 1),
pkt_ratio = SrcPkts / (DstPkts + 1)
)
data <- data %>%
mutate(
hour = hour(StartTime),
duration = as.numeric(LastTime - StartTime),
byte_ratio = SrcBytes / (DstBytes + 1),
pkt_ratio = SrcPkts / (DstPkts + 1)
)
Explicação: Criamos métricas que ajudam na identificação de padrões maliciosos: hour : ataques podem ocorrer em horários específicos duration : ataques DoS tendem a aumentar a duração byte_ratio : scanners enviam muito mais do que recebem pkt_ratio : tráfego anormalmente assimétrico sugere reconhecimento ou varredura
Essas features adicionais tornam a análise mais rica.
numeric_cols <- sapply(data, is.numeric)
data <- data
data[, numeric_cols] <- scale(data[, numeric_cols])
A normalização coloca todas as variáveis na mesma escala. É útil em análises que comparam magnitudes, como gráficos de densidade ou PCA. Isso NÃO altera valores brutos, apenas cria uma versão normalizada opcional.
cols_to_remove <- c(
"SrcAddr", "DstAddr", "sIpId", "dIpId",
"StartTime", "LastTime"
)
data <- data %>% select(-all_of(cols_to_remove))
Retiramos colunas que não ajudam a identificar ataques, pois são metadados e não características comportamentais. Isso deixa o dataset mais limpo e focado nas variáveis que descrevem o comportamento do tráfego.
Verificar estrutura final
str(data)
## tibble [1,194,371 × 47] (S3: tbl_df/tbl/data.frame)
## $ Mean : num [1:1194371] -0.188 -0.188 -0.188 -0.188 4.186 ...
## $ Sport : num [1:1194371] 0.381 0.115 0.776 0.61 -4.538 ...
## $ Dport : num [1:1194371] -0.0875 -0.0875 -0.0875 -0.2154 -0.2397 ...
## $ SrcPkts : num [1:1194371] -0.00297 -0.00297 -0.00297 -0.00309 -0.0029 ...
## $ DstPkts : num [1:1194371] -0.00781 -0.00781 -0.00781 -0.01484 -0.01484 ...
## $ TotPkts : num [1:1194371] -0.00299 -0.00299 -0.00299 -0.00326 -0.00307 ...
## $ DstBytes : num [1:1194371] -0.00945 -0.00945 -0.00945 -0.01012 -0.01012 ...
## $ SrcBytes : num [1:1194371] -0.00396 -0.00396 -0.00396 -0.00404 -0.00391 ...
## $ TotBytes : num [1:1194371] -0.0144 -0.0144 -0.0144 -0.0145 -0.0144 ...
## $ SrcLoad : num [1:1194371] -0.187 -0.187 -0.187 -0.188 -0.188 ...
## $ DstLoad : num [1:1194371] -0.0195 -0.0195 -0.0193 -0.028 -0.028 ...
## $ Load : num [1:1194371] -0.188 -0.188 -0.188 -0.19 -0.19 ...
## $ SrcRate : num [1:1194371] -0.186 -0.186 -0.186 -0.187 -0.187 ...
## $ DstRate : num [1:1194371] -0.0184 -0.0183 -0.0182 -0.027 -0.027 ...
## $ Rate : num [1:1194371] -0.187 -0.187 -0.187 -0.189 -0.189 ...
## $ SrcLoss : num [1:1194371] -0.0108 -0.0108 -0.0108 0.0271 -0.0867 ...
## $ DstLoss : num [1:1194371] -0.0108 -0.0108 -0.0108 -0.0488 -0.0488 ...
## $ Loss : num [1:1194371] -0.00125 -0.00125 -0.00125 -0.00165 -0.00283 ...
## $ pLoss : num [1:1194371] -0.242 -0.242 -0.242 2.813 -2.494 ...
## $ SrcJitter : num [1:1194371] 0.123 -0.965 0.114 -0.107 0.118 ...
## $ DstJitter : num [1:1194371] -0.01606 -0.00453 -0.01082 -0.09314 -0.09314 ...
## $ SIntPkt : num [1:1194371] -0.137 -0.134 -0.137 0.383 0.428 ...
## $ DIntPkt : num [1:1194371] -0.0126 -0.0136 -0.0141 -0.1172 -0.1172 ...
## $ Proto : num [1:1194371] -0.0549 -0.0549 -0.0549 -0.0549 1.2109 ...
## $ Dur : num [1:1194371] -1.62e-03 -1.62e-03 -1.62e-03 6.83e-05 5.34e-03 ...
## $ TcpRtt : num [1:1194371] -0.0103 -0.0095 -0.0194 -0.0334 -0.0334 ...
## $ IdleTime : num [1:1194371] 0.0173 0.0206 0.0204 0.0173 0.0205 ...
## $ Sum : num [1:1194371] -0.184 -0.184 -0.185 0.866 4.143 ...
## $ Min : num [1:1194371] -0.184 -0.184 -0.185 0.866 4.143 ...
## $ Max : num [1:1194371] -0.184 -0.184 -0.185 0.866 4.143 ...
## $ sDSb : num [1:1194371] -0.0102 -0.0102 -0.0102 -0.0102 -0.0102 ...
## $ sTtl : num [1:1194371] -0.0392 -0.0392 -0.0392 0.4485 -5.2412 ...
## $ dTtl : num [1:1194371] 0.306 0.306 0.306 -3.129 -3.129 ...
## $ SAppBytes : num [1:1194371] -0.0685 -0.0685 -0.0685 -0.0769 0.09 ...
## $ DAppBytes : num [1:1194371] -0.00944 -0.00944 -0.00944 -0.00947 -0.00947 ...
## $ TotAppByte: num [1:1194371] -0.0158 -0.0158 -0.0158 -0.0158 -0.0158 ...
## $ SynAck : num [1:1194371] -0.01141 -0.00901 -0.02027 -0.03285 -0.03285 ...
## $ RunTime : num [1:1194371] -0.184 -0.184 -0.185 0.866 4.143 ...
## $ sTos : num [1:1194371] -0.0101 -0.0101 -0.0101 -0.0101 -0.0101 ...
## $ SrcJitAct : num [1:1194371] -0.149 -0.149 -0.149 0.863 1.118 ...
## $ DstJitAct : num [1:1194371] -0.0531 -0.0531 -0.0531 -0.0531 -0.0531 ...
## $ Traffic : Factor w/ 5 levels "Backdoor","CommInj",..: 4 4 4 3 4 3 4 3 4 4 ...
## $ Target : Factor w/ 2 levels "0","1": 1 1 1 2 1 2 1 2 1 1 ...
## $ hour : num [1:1194371] -0.376 1.076 0.108 -0.376 0.592 ...
## $ duration : num [1:1194371] -0.00994 -0.00994 -0.00994 0.05656 0.25606 ...
## $ byte_ratio: num [1:1194371] -0.00383 -0.00383 -0.00383 -0.00378 -0.00364 ...
## $ pkt_ratio : num [1:1194371] -0.133 -0.133 -0.133 0.148 1.118 ...