web3crit-scanner 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +685 -0
- package/bin/web3crit +10 -0
- package/package.json +59 -0
- package/src/analyzers/control-flow.js +256 -0
- package/src/analyzers/data-flow.js +720 -0
- package/src/analyzers/exploit-chain.js +751 -0
- package/src/analyzers/immunefi-classifier.js +515 -0
- package/src/analyzers/poc-validator.js +396 -0
- package/src/analyzers/solodit-enricher.js +1122 -0
- package/src/cli.js +546 -0
- package/src/detectors/access-control-enhanced.js +458 -0
- package/src/detectors/base-detector.js +213 -0
- package/src/detectors/callback-reentrancy.js +362 -0
- package/src/detectors/cross-contract-reentrancy.js +697 -0
- package/src/detectors/delegatecall.js +167 -0
- package/src/detectors/deprecated-functions.js +62 -0
- package/src/detectors/flash-loan.js +408 -0
- package/src/detectors/frontrunning.js +553 -0
- package/src/detectors/gas-griefing.js +701 -0
- package/src/detectors/governance-attacks.js +366 -0
- package/src/detectors/integer-overflow.js +487 -0
- package/src/detectors/oracle-manipulation.js +524 -0
- package/src/detectors/permit-exploits.js +368 -0
- package/src/detectors/precision-loss.js +408 -0
- package/src/detectors/price-manipulation-advanced.js +548 -0
- package/src/detectors/proxy-vulnerabilities.js +651 -0
- package/src/detectors/readonly-reentrancy.js +473 -0
- package/src/detectors/rebasing-token-vault.js +416 -0
- package/src/detectors/reentrancy-enhanced.js +359 -0
- package/src/detectors/selfdestruct.js +259 -0
- package/src/detectors/share-manipulation.js +412 -0
- package/src/detectors/signature-replay.js +409 -0
- package/src/detectors/storage-collision.js +446 -0
- package/src/detectors/timestamp-dependence.js +494 -0
- package/src/detectors/toctou.js +427 -0
- package/src/detectors/token-standard-compliance.js +465 -0
- package/src/detectors/unchecked-call.js +214 -0
- package/src/detectors/vault-inflation.js +421 -0
- package/src/index.js +42 -0
- package/src/package-lock.json +2874 -0
- package/src/package.json +39 -0
- package/src/scanner-enhanced.js +816 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
const BaseDetector = require('./base-detector');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Advanced Price Manipulation Detector
|
|
5
|
+
*
|
|
6
|
+
* Detects sophisticated price manipulation vectors in high-TVL DeFi:
|
|
7
|
+
* - Multi-block price manipulation (sustained manipulation)
|
|
8
|
+
* - Cross-pool arbitrage exploitation
|
|
9
|
+
* - Curve/Balancer-specific vulnerabilities
|
|
10
|
+
* - Liquidation price manipulation
|
|
11
|
+
* - LP token price manipulation
|
|
12
|
+
*
|
|
13
|
+
* Based on real Immunefi bounties:
|
|
14
|
+
* - Cream Finance ($130M) - Oracle manipulation
|
|
15
|
+
* - Harvest Finance ($34M) - USDC/USDT pool manipulation
|
|
16
|
+
* - Warp Finance ($7.7M) - LP token pricing
|
|
17
|
+
* - Inverse Finance ($15M) - Price oracle attack
|
|
18
|
+
*
|
|
19
|
+
* Immunefi Critical: Direct theft via price manipulation
|
|
20
|
+
*/
|
|
21
|
+
class AdvancedPriceManipulationDetector extends BaseDetector {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(
|
|
24
|
+
'Advanced Price Manipulation',
|
|
25
|
+
'Detects sophisticated price manipulation vulnerabilities in DeFi',
|
|
26
|
+
'CRITICAL'
|
|
27
|
+
);
|
|
28
|
+
this.currentContract = null;
|
|
29
|
+
this.currentFunction = null;
|
|
30
|
+
this.priceReads = [];
|
|
31
|
+
this.valueOperations = [];
|
|
32
|
+
this.poolInteractions = [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async detect(ast, sourceCode, fileName, cfg, dataFlow) {
|
|
36
|
+
this.findings = [];
|
|
37
|
+
this.ast = ast;
|
|
38
|
+
this.sourceCode = sourceCode;
|
|
39
|
+
this.fileName = fileName;
|
|
40
|
+
this.sourceLines = sourceCode.split('\n');
|
|
41
|
+
this.cfg = cfg;
|
|
42
|
+
this.dataFlow = dataFlow;
|
|
43
|
+
this.priceReads = [];
|
|
44
|
+
this.valueOperations = [];
|
|
45
|
+
this.poolInteractions = [];
|
|
46
|
+
|
|
47
|
+
// Detect protocol patterns
|
|
48
|
+
this.detectProtocolPatterns();
|
|
49
|
+
|
|
50
|
+
this.traverse(ast);
|
|
51
|
+
this.analyzePriceManipulationVectors();
|
|
52
|
+
|
|
53
|
+
return this.findings;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detect which DeFi protocols are being used
|
|
58
|
+
*/
|
|
59
|
+
detectProtocolPatterns() {
|
|
60
|
+
const code = this.sourceCode;
|
|
61
|
+
|
|
62
|
+
this.protocols = {
|
|
63
|
+
uniswapV2: /IUniswapV2|UniswapV2|getReserves|addLiquidity|removeLiquidity/i.test(code),
|
|
64
|
+
uniswapV3: /IUniswapV3|UniswapV3|slot0|sqrtPriceX96|observe/i.test(code),
|
|
65
|
+
curve: /ICurve|CurvePool|get_dy|get_virtual_price|exchange/i.test(code),
|
|
66
|
+
balancer: /IBalancer|BalancerVault|getPoolTokens|flashLoan/i.test(code),
|
|
67
|
+
aave: /IAave|LendingPool|getReserveData|getUserAccountData/i.test(code),
|
|
68
|
+
compound: /IComptroller|ICToken|getAccountLiquidity|exchangeRate/i.test(code),
|
|
69
|
+
chainlink: /AggregatorV3|latestRoundData|latestAnswer/i.test(code),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
this.isLendingProtocol = this.protocols.aave || this.protocols.compound ||
|
|
73
|
+
/borrow|liquidat|collateral|healthFactor/i.test(code);
|
|
74
|
+
|
|
75
|
+
this.isAMM = this.protocols.uniswapV2 || this.protocols.uniswapV3 ||
|
|
76
|
+
this.protocols.curve || this.protocols.balancer;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
visitContractDefinition(node) {
|
|
80
|
+
this.currentContract = node.name;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
visitFunctionDefinition(node) {
|
|
84
|
+
this.currentFunction = node.name || 'constructor';
|
|
85
|
+
|
|
86
|
+
if (!node.body) return;
|
|
87
|
+
if (node.visibility === 'private' || node.visibility === 'internal') return;
|
|
88
|
+
|
|
89
|
+
const funcCode = this.getCodeSnippet(node.loc);
|
|
90
|
+
const funcName = (node.name || '').toLowerCase();
|
|
91
|
+
|
|
92
|
+
// Detect LP token pricing issues
|
|
93
|
+
this.detectLPTokenPricing(funcCode, node);
|
|
94
|
+
|
|
95
|
+
// Detect Curve-specific vulnerabilities
|
|
96
|
+
this.detectCurveVulnerabilities(funcCode, node);
|
|
97
|
+
|
|
98
|
+
// Detect Balancer-specific vulnerabilities
|
|
99
|
+
this.detectBalancerVulnerabilities(funcCode, node);
|
|
100
|
+
|
|
101
|
+
// Detect liquidation price manipulation
|
|
102
|
+
this.detectLiquidationManipulation(funcCode, node);
|
|
103
|
+
|
|
104
|
+
// Detect cross-pool price reliance
|
|
105
|
+
this.detectCrossPoolReliance(funcCode, node);
|
|
106
|
+
|
|
107
|
+
// Detect same-block price usage
|
|
108
|
+
this.detectSameBlockPricing(funcCode, node);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Detect LP token pricing vulnerabilities
|
|
113
|
+
* Attack: Manipulate reserves → inflate/deflate LP token price → exploit
|
|
114
|
+
*/
|
|
115
|
+
detectLPTokenPricing(funcCode, node) {
|
|
116
|
+
// LP token value = sqrt(reserve0 * reserve1) * 2 / totalSupply
|
|
117
|
+
// Or: LP token value = (reserve0/supply + reserve1/supply) * prices
|
|
118
|
+
const lpPricingPatterns = [
|
|
119
|
+
/getReserves.*totalSupply|totalSupply.*getReserves/i,
|
|
120
|
+
/sqrt\s*\(.*reserve.*reserve/i,
|
|
121
|
+
/lpToken.*price|price.*lpToken/i,
|
|
122
|
+
/underlying.*lp|lp.*underlying/i,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const hasLPPricing = lpPricingPatterns.some(p => p.test(funcCode));
|
|
126
|
+
|
|
127
|
+
if (hasLPPricing) {
|
|
128
|
+
// Check if price is used in value operations
|
|
129
|
+
const usedForValue = /collateral|borrow|liquidat|mint.*share|withdraw/i.test(funcCode);
|
|
130
|
+
|
|
131
|
+
if (usedForValue) {
|
|
132
|
+
// Check for manipulation protection
|
|
133
|
+
const hasProtection = /twap|average|delay|snapshot|checkpoint/i.test(funcCode);
|
|
134
|
+
|
|
135
|
+
if (!hasProtection) {
|
|
136
|
+
this.addFinding({
|
|
137
|
+
title: 'LP Token Price Manipulation Vulnerability',
|
|
138
|
+
description: `Function '${this.currentFunction}' uses LP token pricing for value operations without manipulation protection.\n\n` +
|
|
139
|
+
`Attack (Warp Finance style):\n` +
|
|
140
|
+
`1. Flash loan large amount of underlying tokens\n` +
|
|
141
|
+
`2. Add to pool, manipulating reserves\n` +
|
|
142
|
+
`3. LP token price artificially inflated\n` +
|
|
143
|
+
`4. Use overvalued LP as collateral or calculate shares\n` +
|
|
144
|
+
`5. Extract value (borrow, mint shares, etc.)\n` +
|
|
145
|
+
`6. Remove liquidity, repay flash loan\n\n` +
|
|
146
|
+
`Real-world exploits:\n` +
|
|
147
|
+
`- Warp Finance: $7.7M stolen\n` +
|
|
148
|
+
`- Value DeFi: $6M stolen\n` +
|
|
149
|
+
`- Cheese Bank: $3.3M stolen`,
|
|
150
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
151
|
+
line: node.loc?.start?.line || 0,
|
|
152
|
+
code: funcCode.substring(0, 400),
|
|
153
|
+
severity: 'CRITICAL',
|
|
154
|
+
confidence: 'HIGH',
|
|
155
|
+
exploitable: true,
|
|
156
|
+
exploitabilityScore: 95,
|
|
157
|
+
attackVector: 'lp-token-manipulation',
|
|
158
|
+
recommendation: `1. Use TWAP for LP token pricing\n` +
|
|
159
|
+
`2. Use fair LP pricing: 2 * sqrt(r0 * r1) / supply (Alpha Homora formula)\n` +
|
|
160
|
+
`3. Add minimum liquidity checks\n` +
|
|
161
|
+
`4. Verify reserves haven't changed in same block\n` +
|
|
162
|
+
`5. Consider Chainlink LP token price feeds`,
|
|
163
|
+
references: [
|
|
164
|
+
'https://cmichel.io/pricing-lp-tokens/',
|
|
165
|
+
'https://blog.alphafinance.io/fair-lp-token-pricing/'
|
|
166
|
+
],
|
|
167
|
+
foundryPoC: this.generateLPManipulationPoC()
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Detect Curve-specific vulnerabilities
|
|
176
|
+
*/
|
|
177
|
+
detectCurveVulnerabilities(funcCode, node) {
|
|
178
|
+
if (!this.protocols.curve) return;
|
|
179
|
+
|
|
180
|
+
// Curve virtual price manipulation
|
|
181
|
+
const usesVirtualPrice = /get_virtual_price|virtualPrice/i.test(funcCode);
|
|
182
|
+
|
|
183
|
+
if (usesVirtualPrice) {
|
|
184
|
+
// Check if used during reentrancy-susceptible operations
|
|
185
|
+
const hasReentrancyRisk = /\.call\s*\{|withdraw|remove_liquidity/i.test(funcCode);
|
|
186
|
+
|
|
187
|
+
if (hasReentrancyRisk) {
|
|
188
|
+
this.addFinding({
|
|
189
|
+
title: 'Curve Read-Only Reentrancy via virtual_price',
|
|
190
|
+
description: `Function '${this.currentFunction}' reads Curve virtual_price which is vulnerable to read-only reentrancy.\n\n` +
|
|
191
|
+
`Attack (2022 Curve/Vyper exploits):\n` +
|
|
192
|
+
`1. Call remove_liquidity on Curve pool\n` +
|
|
193
|
+
`2. During callback (ETH transfer), reenter victim contract\n` +
|
|
194
|
+
`3. Victim reads stale virtual_price (not yet updated)\n` +
|
|
195
|
+
`4. Virtual price appears higher than actual\n` +
|
|
196
|
+
`5. Attacker profits from price discrepancy\n\n` +
|
|
197
|
+
`This affected multiple protocols integrating Curve.`,
|
|
198
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
199
|
+
line: node.loc?.start?.line || 0,
|
|
200
|
+
code: funcCode.substring(0, 300),
|
|
201
|
+
severity: 'CRITICAL',
|
|
202
|
+
confidence: 'HIGH',
|
|
203
|
+
exploitable: true,
|
|
204
|
+
exploitabilityScore: 90,
|
|
205
|
+
attackVector: 'curve-readonly-reentrancy',
|
|
206
|
+
recommendation: `1. Add reentrancy guard that covers virtual_price reads\n` +
|
|
207
|
+
`2. Use Curve's reentrancy lock: raw_call(pool, method_id("claim_admin_fees()"))\n` +
|
|
208
|
+
`3. Cache virtual_price at start of transaction\n` +
|
|
209
|
+
`4. Don't use virtual_price for immediate pricing decisions`,
|
|
210
|
+
references: [
|
|
211
|
+
'https://chainsecurity.com/heartbreak-curve-lp-oracle-manipulation/'
|
|
212
|
+
]
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Curve get_dy spot price
|
|
218
|
+
const usesGetDy = /get_dy\s*\(|calc_token_amount/i.test(funcCode);
|
|
219
|
+
|
|
220
|
+
if (usesGetDy) {
|
|
221
|
+
const usedForValue = /collateral|borrow|mint|price/i.test(funcCode);
|
|
222
|
+
|
|
223
|
+
if (usedForValue) {
|
|
224
|
+
this.addFinding({
|
|
225
|
+
title: 'Curve Spot Price (get_dy) Used for Valuation',
|
|
226
|
+
description: `Function '${this.currentFunction}' uses Curve's get_dy for pricing, which returns spot price and is manipulable.\n\n` +
|
|
227
|
+
`Issue: get_dy returns current exchange rate, which can be manipulated by:\n` +
|
|
228
|
+
`1. Large swaps that move the curve\n` +
|
|
229
|
+
`2. Flash loans providing temporary liquidity\n` +
|
|
230
|
+
`3. Imbalanced pool additions\n\n` +
|
|
231
|
+
`For valuation, use oracle prices or time-weighted calculations.`,
|
|
232
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
233
|
+
line: node.loc?.start?.line || 0,
|
|
234
|
+
code: funcCode.substring(0, 200),
|
|
235
|
+
severity: 'HIGH',
|
|
236
|
+
confidence: 'MEDIUM',
|
|
237
|
+
exploitable: true,
|
|
238
|
+
exploitabilityScore: 75,
|
|
239
|
+
attackVector: 'curve-spot-price',
|
|
240
|
+
recommendation: `1. Use Chainlink price feeds for token valuation\n` +
|
|
241
|
+
`2. Implement TWAP over Curve trades\n` +
|
|
242
|
+
`3. Add slippage protection for swaps`
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Detect Balancer-specific vulnerabilities
|
|
250
|
+
*/
|
|
251
|
+
detectBalancerVulnerabilities(funcCode, node) {
|
|
252
|
+
if (!this.protocols.balancer) return;
|
|
253
|
+
|
|
254
|
+
// Balancer flash loan + pool manipulation
|
|
255
|
+
const hasFlashLoan = /flashLoan|onFlashLoan|receiveFlashLoan/i.test(funcCode);
|
|
256
|
+
const hasPoolOps = /getPoolTokens|joinPool|exitPool/i.test(funcCode);
|
|
257
|
+
|
|
258
|
+
if (hasFlashLoan && hasPoolOps) {
|
|
259
|
+
this.addFinding({
|
|
260
|
+
title: 'Balancer Flash Loan Pool Manipulation Risk',
|
|
261
|
+
description: `Function '${this.currentFunction}' combines Balancer flash loans with pool operations.\n\n` +
|
|
262
|
+
`Risk: Balancer flash loans are free (no fee for returning same block), enabling:\n` +
|
|
263
|
+
`1. Borrow all pool tokens via flash loan\n` +
|
|
264
|
+
`2. Manipulate pool balances/prices\n` +
|
|
265
|
+
`3. Execute exploit during manipulation\n` +
|
|
266
|
+
`4. Return tokens\n\n` +
|
|
267
|
+
`Ensure price reads don't occur during flashloan-susceptible states.`,
|
|
268
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
269
|
+
line: node.loc?.start?.line || 0,
|
|
270
|
+
code: funcCode.substring(0, 300),
|
|
271
|
+
severity: 'HIGH',
|
|
272
|
+
confidence: 'MEDIUM',
|
|
273
|
+
exploitable: true,
|
|
274
|
+
exploitabilityScore: 70,
|
|
275
|
+
attackVector: 'balancer-flash-manipulation',
|
|
276
|
+
recommendation: `1. Don't read pool prices during flash loan callbacks\n` +
|
|
277
|
+
`2. Verify pool state hasn't changed unexpectedly\n` +
|
|
278
|
+
`3. Use external oracles for pricing\n` +
|
|
279
|
+
`4. Add reentrancy protection`
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Balancer getRate for valuation
|
|
284
|
+
const usesGetRate = /getRate\s*\(|getRateProviders/i.test(funcCode);
|
|
285
|
+
|
|
286
|
+
if (usesGetRate) {
|
|
287
|
+
const usedForCollateral = /collateral|borrow|health|liquidat/i.test(funcCode);
|
|
288
|
+
|
|
289
|
+
if (usedForCollateral) {
|
|
290
|
+
this.addFinding({
|
|
291
|
+
title: 'Balancer Rate Provider Used for Collateral Valuation',
|
|
292
|
+
description: `Function uses Balancer rate provider for collateral valuation.\n\n` +
|
|
293
|
+
`Risk: Rate providers can be manipulated or become stale.\n` +
|
|
294
|
+
`Some rate providers read from pools that can be manipulated.`,
|
|
295
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
296
|
+
line: node.loc?.start?.line || 0,
|
|
297
|
+
severity: 'MEDIUM',
|
|
298
|
+
confidence: 'MEDIUM',
|
|
299
|
+
exploitable: true,
|
|
300
|
+
exploitabilityScore: 60,
|
|
301
|
+
attackVector: 'balancer-rate-manipulation',
|
|
302
|
+
recommendation: `1. Verify rate provider source is manipulation-resistant\n` +
|
|
303
|
+
`2. Add sanity bounds on rate changes\n` +
|
|
304
|
+
`3. Consider backup oracles`
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Detect liquidation price manipulation
|
|
312
|
+
*/
|
|
313
|
+
detectLiquidationManipulation(funcCode, node) {
|
|
314
|
+
if (!this.isLendingProtocol) return;
|
|
315
|
+
|
|
316
|
+
const funcName = (node.name || '').toLowerCase();
|
|
317
|
+
|
|
318
|
+
if (/liquidat/i.test(funcName) || /liquidat/i.test(funcCode)) {
|
|
319
|
+
// Check if liquidation uses spot prices
|
|
320
|
+
const usesSpotPrice = /getReserves|slot0|get_dy|balanceOf.*balanceOf/i.test(funcCode);
|
|
321
|
+
const usesOracle = /latestRoundData|getAssetPrice|oracle/i.test(funcCode);
|
|
322
|
+
|
|
323
|
+
if (usesSpotPrice && !usesOracle) {
|
|
324
|
+
this.addFinding({
|
|
325
|
+
title: 'Liquidation Uses Manipulable Spot Price',
|
|
326
|
+
description: `Liquidation function '${this.currentFunction}' uses spot prices instead of oracles.\n\n` +
|
|
327
|
+
`Attack (Inverse Finance style):\n` +
|
|
328
|
+
`1. Open leveraged position at normal prices\n` +
|
|
329
|
+
`2. Flash loan to manipulate spot price downward\n` +
|
|
330
|
+
`3. Trigger liquidation of victims (including self-liquidation for profit)\n` +
|
|
331
|
+
`4. Restore price, repay flash loan\n` +
|
|
332
|
+
`5. Profit from liquidation bonus\n\n` +
|
|
333
|
+
`Inverse Finance lost $15M to this attack pattern.`,
|
|
334
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
335
|
+
line: node.loc?.start?.line || 0,
|
|
336
|
+
code: funcCode.substring(0, 300),
|
|
337
|
+
severity: 'CRITICAL',
|
|
338
|
+
confidence: 'HIGH',
|
|
339
|
+
exploitable: true,
|
|
340
|
+
exploitabilityScore: 95,
|
|
341
|
+
attackVector: 'liquidation-manipulation',
|
|
342
|
+
recommendation: `1. Use Chainlink oracles for liquidation prices\n` +
|
|
343
|
+
`2. Add circuit breakers for rapid price changes\n` +
|
|
344
|
+
`3. Implement time-delayed liquidations\n` +
|
|
345
|
+
`4. Use TWAP as fallback\n` +
|
|
346
|
+
`5. Add maximum liquidation amounts per block`,
|
|
347
|
+
references: [
|
|
348
|
+
'https://rekt.news/inverse-finance-rekt/'
|
|
349
|
+
]
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check for same-block liquidation
|
|
354
|
+
const allowsSameBlockLiquidation = !/blockNumber.*!=|lastUpdate.*<|delay/i.test(funcCode);
|
|
355
|
+
|
|
356
|
+
if (allowsSameBlockLiquidation && usesOracle) {
|
|
357
|
+
this.addFinding({
|
|
358
|
+
title: 'Same-Block Liquidation Allowed',
|
|
359
|
+
description: `Liquidation in '${this.currentFunction}' can occur in the same block as position changes.\n\n` +
|
|
360
|
+
`Risk:\n` +
|
|
361
|
+
`1. Attacker manipulates oracle in block N\n` +
|
|
362
|
+
`2. Opens position + liquidates victim in same block\n` +
|
|
363
|
+
`3. Oracle manipulation is never observed externally\n\n` +
|
|
364
|
+
`This enables atomic MEV-style liquidation attacks.`,
|
|
365
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
366
|
+
line: node.loc?.start?.line || 0,
|
|
367
|
+
severity: 'HIGH',
|
|
368
|
+
confidence: 'MEDIUM',
|
|
369
|
+
exploitable: true,
|
|
370
|
+
exploitabilityScore: 70,
|
|
371
|
+
attackVector: 'same-block-liquidation',
|
|
372
|
+
recommendation: `1. Require minimum time between position change and liquidation\n` +
|
|
373
|
+
`2. Use block.number checks to prevent same-block liquidation\n` +
|
|
374
|
+
`3. Implement gradual liquidation with time delays`
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Detect cross-pool price reliance
|
|
382
|
+
*/
|
|
383
|
+
detectCrossPoolReliance(funcCode, node) {
|
|
384
|
+
// Multiple pool reads in same function
|
|
385
|
+
const poolPatterns = [
|
|
386
|
+
/getReserves/gi,
|
|
387
|
+
/slot0/gi,
|
|
388
|
+
/get_dy/gi,
|
|
389
|
+
/getPoolTokens/gi,
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
let poolReadCount = 0;
|
|
393
|
+
for (const pattern of poolPatterns) {
|
|
394
|
+
const matches = funcCode.match(pattern);
|
|
395
|
+
if (matches) poolReadCount += matches.length;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (poolReadCount >= 2) {
|
|
399
|
+
// Multiple pools read - check for arbitrage/manipulation risk
|
|
400
|
+
this.addFinding({
|
|
401
|
+
title: 'Cross-Pool Price Dependency Detected',
|
|
402
|
+
description: `Function '${this.currentFunction}' reads prices from multiple pools.\n\n` +
|
|
403
|
+
`Risk: Cross-pool manipulation attack:\n` +
|
|
404
|
+
`1. Attacker manipulates Pool A price up\n` +
|
|
405
|
+
`2. Contract reads Pool A price for valuation\n` +
|
|
406
|
+
`3. Attacker uses overvalued position\n` +
|
|
407
|
+
`4. Attacker manipulates Pool B for arbitrage profit\n\n` +
|
|
408
|
+
`This creates complex manipulation opportunities.`,
|
|
409
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
410
|
+
line: node.loc?.start?.line || 0,
|
|
411
|
+
code: funcCode.substring(0, 300),
|
|
412
|
+
severity: 'HIGH',
|
|
413
|
+
confidence: 'MEDIUM',
|
|
414
|
+
exploitable: true,
|
|
415
|
+
exploitabilityScore: 65,
|
|
416
|
+
attackVector: 'cross-pool-manipulation',
|
|
417
|
+
recommendation: `1. Use same oracle source for related assets\n` +
|
|
418
|
+
`2. Add cross-pool price deviation checks\n` +
|
|
419
|
+
`3. Implement maximum price impact limits\n` +
|
|
420
|
+
`4. Consider circuit breakers for extreme deviations`
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Detect same-block price usage issues
|
|
427
|
+
*/
|
|
428
|
+
detectSameBlockPricing(funcCode, node) {
|
|
429
|
+
// Check if price is read and used without block delay
|
|
430
|
+
const readsPriceOrReserves = /getReserves|slot0|latestRoundData|getAssetPrice|balanceOf/i.test(funcCode);
|
|
431
|
+
const hasValueOperation = /mint|burn|swap|borrow|withdraw|deposit/i.test(funcCode);
|
|
432
|
+
const hasBlockCheck = /block\.number\s*!=|lastBlock|blockDelay|_lastUpdateBlock/i.test(funcCode);
|
|
433
|
+
|
|
434
|
+
if (readsPriceOrReserves && hasValueOperation && !hasBlockCheck) {
|
|
435
|
+
// Check if it's a swap function (expected behavior)
|
|
436
|
+
const funcName = (node.name || '').toLowerCase();
|
|
437
|
+
if (/^swap|^exchange/i.test(funcName)) {
|
|
438
|
+
return; // Swaps need current price
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this.addFinding({
|
|
442
|
+
title: 'Price Used Without Block Delay Protection',
|
|
443
|
+
description: `Function '${this.currentFunction}' reads prices and performs value operations in potentially same block.\n\n` +
|
|
444
|
+
`Risk: Atomic manipulation attack:\n` +
|
|
445
|
+
`1. Manipulate price in transaction 1 of block\n` +
|
|
446
|
+
`2. Call vulnerable function in transaction 2\n` +
|
|
447
|
+
`3. Restore price in transaction 3\n` +
|
|
448
|
+
`All in same block, appearing atomic to external observers.`,
|
|
449
|
+
location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
|
|
450
|
+
line: node.loc?.start?.line || 0,
|
|
451
|
+
severity: 'MEDIUM',
|
|
452
|
+
confidence: 'LOW',
|
|
453
|
+
exploitable: true,
|
|
454
|
+
exploitabilityScore: 55,
|
|
455
|
+
attackVector: 'same-block-pricing',
|
|
456
|
+
recommendation: `1. Track last update block for price-sensitive operations\n` +
|
|
457
|
+
`2. Require price to be at least 1 block old\n` +
|
|
458
|
+
`3. Use commit-reveal pattern for large operations`
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Analyze collected price manipulation vectors
|
|
465
|
+
*/
|
|
466
|
+
analyzePriceManipulationVectors() {
|
|
467
|
+
// Check for dangerous protocol combinations
|
|
468
|
+
if (this.protocols.uniswapV2 && this.isLendingProtocol) {
|
|
469
|
+
// UniV2 spot price + lending = classic attack surface
|
|
470
|
+
const hasOracleProtection = this.protocols.chainlink ||
|
|
471
|
+
/twap|timeWeighted|average/i.test(this.sourceCode);
|
|
472
|
+
|
|
473
|
+
if (!hasOracleProtection) {
|
|
474
|
+
this.addFinding({
|
|
475
|
+
title: 'Lending Protocol Uses Uniswap V2 Spot Prices',
|
|
476
|
+
description: `Contract combines lending functionality with Uniswap V2 price reads without oracle protection.\n\n` +
|
|
477
|
+
`This is the classic attack pattern used in:\n` +
|
|
478
|
+
`- bZx ($8M)\n` +
|
|
479
|
+
`- Harvest Finance ($34M)\n` +
|
|
480
|
+
`- Cream Finance ($130M)\n\n` +
|
|
481
|
+
`UniV2 reserves can be manipulated within a single transaction via flash loans.`,
|
|
482
|
+
location: `Contract: ${this.currentContract}`,
|
|
483
|
+
line: 1,
|
|
484
|
+
severity: 'CRITICAL',
|
|
485
|
+
confidence: 'HIGH',
|
|
486
|
+
exploitable: true,
|
|
487
|
+
exploitabilityScore: 95,
|
|
488
|
+
attackVector: 'univ2-lending-manipulation',
|
|
489
|
+
recommendation: `CRITICAL: Never use UniV2 spot prices for lending:\n` +
|
|
490
|
+
`1. Use Chainlink price feeds\n` +
|
|
491
|
+
`2. Implement UniV2 TWAP (30+ minute window)\n` +
|
|
492
|
+
`3. Add price deviation circuit breakers\n` +
|
|
493
|
+
`4. Consider using UniV3 TWAP`,
|
|
494
|
+
references: [
|
|
495
|
+
'https://samczsun.com/taking-undercollateralized-loans-for-fun-and-for-profit/'
|
|
496
|
+
]
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
generateLPManipulationPoC() {
|
|
503
|
+
return `// SPDX-License-Identifier: MIT
|
|
504
|
+
pragma solidity ^0.8.0;
|
|
505
|
+
|
|
506
|
+
import "forge-std/Test.sol";
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* PoC: LP Token Price Manipulation (Warp Finance style)
|
|
510
|
+
*/
|
|
511
|
+
contract LPManipulationExploit is Test {
|
|
512
|
+
// ILendingProtocol lending;
|
|
513
|
+
// IUniswapV2Pair lpToken;
|
|
514
|
+
// IERC20 token0;
|
|
515
|
+
// IERC20 token1;
|
|
516
|
+
|
|
517
|
+
function testLPManipulation() public {
|
|
518
|
+
// Setup: Get flash loan for manipulation capital
|
|
519
|
+
|
|
520
|
+
// Step 1: Add massive liquidity to inflate LP price
|
|
521
|
+
// uint256 loanAmount = 10_000_000e18;
|
|
522
|
+
// token0.approve(address(router), loanAmount);
|
|
523
|
+
// token1.approve(address(router), loanAmount);
|
|
524
|
+
// router.addLiquidity(...);
|
|
525
|
+
|
|
526
|
+
// LP token price is now artificially high
|
|
527
|
+
|
|
528
|
+
// Step 2: Deposit inflated LP as collateral
|
|
529
|
+
// lpToken.approve(address(lending), lpBalance);
|
|
530
|
+
// lending.depositCollateral(address(lpToken), lpBalance);
|
|
531
|
+
|
|
532
|
+
// Step 3: Borrow against inflated collateral
|
|
533
|
+
// uint256 maxBorrow = lending.getMaxBorrow(address(this));
|
|
534
|
+
// lending.borrow(maxBorrow);
|
|
535
|
+
|
|
536
|
+
// Step 4: Remove liquidity (deflates LP price)
|
|
537
|
+
// router.removeLiquidity(...);
|
|
538
|
+
|
|
539
|
+
// Step 5: Repay flash loan
|
|
540
|
+
// Profit = borrowed amount - flash loan fee
|
|
541
|
+
|
|
542
|
+
// Victim: Lending protocol is now undercollateralized
|
|
543
|
+
}
|
|
544
|
+
}`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
module.exports = AdvancedPriceManipulationDetector;
|