Teknisk artikel

HotXLS osäkra formelåteranrop: grind för CALL och WEBSERVICE

HotXLS vägrar skicka 20 farliga formelnamn, bland dem CALL, REGISTER.ID, WEBSERVICE och DDE, till dina Delphi-återanrop för användarfunktioner om du inte uttryckligen aktiverar det. Egenskapen AllowUnsafeFormulaCallbacks på arbetsboken har False som standard, kontrollen körs innan något argument evalueras, och ett avvisat anrop rapporterar xlfeUnsafeFunctionDenied utan att anropa en enda hanterare

Scenariot som gjorde detta nödvändigt är vardagligt. En tjänst tar emot uppladdade XLS- eller XLSX-filer, räknar om dem på servern och läser tillbaka några summor. Värdapplikationen registrerade en OnUserFunction-hanterare för några år sedan för ett par affärsfunktioner, och någonstans på vägen växte den hanteraren fram en catch-all-gren som vidarebefordrar allt den inte känner igen till en plugintabell. Ingen i teamet skrev någonsin =WEBSERVICE(...) i en cell. Uppladdaren gjorde det. Att bevara den formeln orörd genom öppning, omräkning och sparning är filfidelitet, en finess. Att låta den nå värdkod som kan öppna uttag eller filer är däremot ett auktorisationsbeslut, och förrän HotXLS skilde åt de två tog biblioteket det beslutet tyst åt dig

Varför blev att bevara en formel detsamma som tillstånd att köra den?

Rotorsaken var en enda reservväg. HotXLS parsar varje Excel-funktionsnamn den känner till, men inte alla kända namn har en implementering i beräkningsmotorn. Inbyggda funktioner som känns igen men saknar implementering hamnade förr i samma reservväg för användardefinierade funktioner som genuint anpassade namn, så CALL och REGISTER.ID delade en dispatchväg med din DISCOUNT eller REGIONRATE. Okända namn som WEBSERVICE eller DDE kunde på samma sätt matcha en post med samma namn i arbetsbokens register, processens register eller en händelsehanterare. Mekaniken i den reservvägen tas upp i hur HotXLS löser anpassade funktioner via OnUserFunction; problemet var att inget på den vägen frågade om namnet själv var ett som en sund värd överhuvudtaget borde köra

Dispatchordningen spelar roll för vad "okänd" betyder här. Ett anrop motorn inte kan evaluera nativt erbjuds i tur och ordning till lexikala LAMBDA- och LET-bindningar, som stödet för stängningar i HotXLS formelmotor löser först, sedan till arbetsbokslokala funktioner registrerade med RegisterUserFunction, därefter till processomfattande funktioner från TXLSWorkbook.RegisterGlobalUserFunction, och slutligen till händelserna OnUserFunction och OnUserFunctionEx. Först när alla tackar nej blir en verkligt okänd funktion #NAME?. Varje steg efter uppslaget av lambda överlämnar kontrollen till kod du skrivit, vilket är precis varför säkerhetskontrollen måste sitta framför hela kedjan i stället för inuti någon enskild hanterare

Hur HotXLS grindar osäkra formelåteranrop: GetValueItemUserFunction kontrollerar namnet innan argumentarrayen byggs eller någon resolver körs, så ett nästlat =WEBSERVICE(AUDIT_TOKEN()) avslutas med lxErrorUnsafeFunctionDenied och granskningsloggen förblir tom, medan säkra anrop vandrar kedjan från LAMBDA- och LET-bindningar ner till OnUserFunction
Först när varje steg tackar nej blir en verkligt okänd funktion #NAME?, vilket är varför säkerhetskontrollen sitter framför hela kedjan i stället för inuti någon enskild hanterare

Vilka funktionsnamn blockerar HotXLS som standard?

XLSFormulaCallbackIsUnsafe i lxCalc.pas håller en fast förbjuden uppsättning på 20 namn: DDE, CALL, REGISTER, REGISTER.ID, WEBSERVICE, RTD, SQL.REQUEST, EXEC, RUN, CREATE.OBJECT, APP.ACTIVATE, SEND.KEYS, OPEN, SAVE, SAVE.AS, FOPEN, FWRITE, FWRITELN, FCLOSE och FILE.DELETE. Det är namnen som, i Excel eller dess makrospråk, laddar native-kod, når nätverket, pratar med andra processer eller rör vid filsystemet. Före jämförelsen trimmar funktionen omgivande blanktecken, versaler namnet och stryker ett enskilt prefix _XLFN. eller _XLWS., så _xlfn.webservice skrivet av en nyare Excel-version fångas precis som den nakna stavningen. Listan bor vid kalkylatorgränsen snarare än i parserna för Classic, XLSX och ODS, så att en AST, en BIFF-tokenström och en konverterad arbetsbok beter sig identiskt

Hur HotXLS normaliserar ett funktionsnamn före jämförelsen mot osäkra återanrop: blanktecken trimmas, namnet versaliseras och ett enskilt prefix _XLFN. eller _XLWS. stryks så att _xlfn.webservice fångas som den nakna stavningen, därefter matchas resultatet exakt mot den fasta förbjudna uppsättningen på 20 namn i lxCalc.pas
Listan spänner över namnen som laddar native-kod, når nätverket, pratar med andra processer eller rör vid filsystemet, från DDE, CALL och WEBSERVICE till FWRITE och FILE.DELETE

Två kanter är värda att känna till innan du litar på den. Matchen är exakt, så en hanterare du registrerar som MYWEBSERVICE påverkas inte, och omvänt nekas en legitim intern UDF som råkar heta OPEN eller RUN nu som standard. Den förbjudna uppsättningen är heller inte en sandlåda för dina egna hanterare. Om din catch-all-gren kör godtyckliga pluginnamn stoppar grinden de berömda farliga och inget annat; den hållbara fixen är fortfarande en hanterare som matchar en explicit tillåten lista med SameText och lämnar Handled på False för allt den inte äger

Varför måste grinden köras innan argumenten evalueras?

En grind som slår till efter att argumenten räknats ut kommer för sent, för argumenten själva kan anropa din kod. GetValueItemUserFunction kontrollerar namnet först och avslutar med lxErrorUnsafeFunctionDenied innan den bygger argumentarrayen, innan den frågar en resolver eller något av registren, och till och med innan den noterar att ingen hanterare alls är kopplad. Den ordningen är vad som besegrar det nästlade fallet nedan, där det yttre anropet ändå skulle vägras, men en inre UDF som ser oskyldig ut annars hade avfyrats först och lämnat sin bieffekt kvar

procedure TImportService.HandleUdf(Sender: TObject;
  const FunctionName: WideString; const Args: Variant;
  var Value: Variant; var Handled: Boolean);
begin
  if SameText(FunctionName, 'AUDIT_TOKEN') then
  begin
    FAuditLog.Add('AUDIT_TOKEN evaluated');   // bieffekt i värdkod
    Value := 'token-42';
    Handled := True;
  end;
end;

Book.OnUserFunction := HandleUdf;
Eval := Sheet.EvaluateFormulaAt(1, 1, '=WEBSERVICE(AUDIT_TOKEN())');
// Eval.Status = xlfeUnsafeFunctionDenied, Eval.Value = Null,
// Eval.Issue.NativeCode = -106, och FAuditLog är fortfarande tom

Arbetsbokens standard kontra per-anrop TXLSFormulaEvaluationOptions

Arbetsboksflaggan är standarden och per-anrop-optionen har sista ordet. TXLSWorkbook.AllowUnsafeFormulaCallbacks och TXLSXWorkbook.AllowUnsafeFormulaCallbacks styr vanlig omräkning, Calculate, tvåargumentversionen av EvaluateFormulaAt, evalueringsmallar, skrivskyddade vyer och, på XLSX, varje arbetare i den parallella omräkningspoolen. Varje ingångspunkt som tar emot en explicit TXLSFormulaEvaluationOptions-post använder Options.AllowUnsafeFormulaCallbacks som dom för det anropet och kombinerar den inte med arbetsboksegenskapen. Den asymmetrin är medveten: ett betrott internt jobb kan auktorisera ett enda RTD-uppslag utan att vända hela arbetsboken, och en arbetsbok som är globalt aktiverad kan fortfarande tvinga en känslig evaluering tillbaka till nekande

var
  Options: TXLSFormulaEvaluationOptions;
  Eval: TXLSFormulaEvaluationResult;
begin
  // arbetsboken förblir låst, ett betrott anrop släpps igenom
  Book.AllowUnsafeFormulaCallbacks := False;
  Options := XLSDefaultFormulaEvaluationOptions;
  Options.AllowUnsafeFormulaCallbacks := True;
  Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);

  // arbetsboken aktiverad, men denna evaluering av uppladdad text är det inte
  Book.AllowUnsafeFormulaCallbacks := True;
  Options := XLSDefaultFormulaEvaluationOptions;   // flaggan är False igen
  Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
  if Eval.Status = xlfeUnsafeFunctionDenied then
    LogRejected(Eval.Issue.Message);
end;

Att växla arbetsboksegenskapen märker också beroendegrafen smutsig i båda motorerna. Utan det steget skulle ett cachat resultat beräknat medan återanrop var tillåtna kunna serveras efter att de återkallats, eller så kunde ett cachat utfall xlfeUnsafeFunctionDenied överleva en aktivering. Den nya statusen lades till TXLSFormulaEvaluationStatus efter xlfeFailed, så den har ordinal 10 och varje befintlig ordinal behåller sitt värde; samma regel om tillägg på svansen gäller fältet i optionsposten och gettern och settern i IXLSWorkbook, även om en konsument byggd mot en äldre utgåva fortfarande behöver kompileras om

Vad händer med okänd och osäker formeltext vid sparning?

Att behålla en formel och att köra den är nu två skilda frågor, och ingångspolicyn svarar bara på den första. FormulaEntryPolicy på vardera arbetsboksklassen bär UnknownFunctionMode och UnknownNameMode, båda med xlfusmReject som standard, så att tilldela en formel med ett okänt anrop via den vanliga egenskapen Formula avvisas innan cellvärdet, formelcachen eller beroendena ändras. ValidateFormulaEntry rapporterar samma beslut utan biverkningar. Betrodda vägar som filläsning, kopiering och formatkonvertering kringgår den användaringångspolicyn, för en strikt standard får aldrig avvisa symboler som redan finns i en fil du bara öppnar

var
  Policy: TXLSFormulaEntryPolicy;
begin
  Policy := Book.FormulaEntryPolicy;
  Policy.UnknownFunctionMode := xlfusmPreserve;   // kompatibilitetsingång
  Book.FormulaEntryPolicy := Policy;
  Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)';  // lagrad, inte auktoriserad
  Book.SaveAs('rates.xls');
end;

I Classic BIFF8 har ett okänt anrop ingen egen token, så HotXLS skriver det som Excel skriver tilläggsfunktioner. Formeln får en PtgNameX-token ($59) vars XTI-post pekar på tilläggets SUPBOOK med båda bladindexen satta till $FFFE, följt av argumenttokenen och en PtgFuncVar som bär funktionsnummer 255 och ett argumentantal som inkluderar namnplatsen. Den underliggande ExternName-kroppen är sex nollbyte, en längdbyte och Unicodeflagga, UTF-16-namnet, och därefter en tvåbytesformel av $1C $17, en PtgErr som håller #REF!. Skrivaren vägrar namn längre än 255 tecken, fler än 29 argument och BIFF5-målet. Hur HotXLS klassificerar dessa tilläggs-SUPBOOK-poster bredvid externa arbetsbokslänkar förklaras i SUPBOOK- och XTI-klassificeringsreglerna för BIFF externa länkar. XLSX behåller den råa funktionstexten och ODS behåller sin msoxl:-formel, och i varje format öppnas en fil som sparade =WEBSERVICE(...) igen med texten intakt och evaluerar fortfarande till xlfeUnsafeFunctionDenied som standard

Hur HotXLS skriver ett okänt formelanrop till klassisk BIFF8: formeln bär en PtgNameX-token vars XTI-post pekar på tilläggets SUPBOOK med båda bladindexen $FFFE, därefter argumenttokenen och en PtgFuncVar med funktionsnummer 255, backad av en ExternName-kropp som slutar med en tvåbytes $1C $17-PtgErr som håller #REF!
XLSX behåller den råa funktionstexten och ODS behåller sin msoxl:-formel, så en fil som sparade =WEBSERVICE(...) öppnas igen med texten intakt och evaluerar fortfarande till xlfeUnsafeFunctionDenied som standard

Evaluerar din pipeline arbetsböcker den inte själv författat, låt AllowUnsafeFormulaCallbacks stå på False, håll hanterare på en explicit tillåten lista, och bevilja per-anrop-optioner bara där formelkällan är din egen. Hela återanrops-, ingångspolicy- och evaluerings-API:et är dokumenterat hos HotXLS Delphi-kalkylbladskomponenten